Initial fork of version 1.8 from Rugged Circuits

This commit is contained in:
Scott Daniels 2013-03-31 02:24:39 -05:00
commit d49ecb7c57
72 changed files with 18806 additions and 0 deletions

0
.gitignore vendored Normal file
View File

674
COPYING Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

23
PKG-INFO Normal file
View File

@ -0,0 +1,23 @@
Metadata-Version: 1.0
Name: gerbmerge
Version: 1.8
Summary: Merge multiple Gerber/Excellon files
Home-page: http://ruggedcircuits.com/gerbmerge
Author: Rugged Circuits LLC
Author-email: support@ruggedcircuits.com
License: GPL
Description: GerbMerge is a program that combines several Gerber
(i.e., RS274-X) and Excellon files into a single set
of files. This program is useful for combining multiple
printed circuit board layout files into a single job.
To run the program, invoke the Python interpreter on the
gerbmerge.py file. On Windows, if you installed GerbMerge in
C:/Python24, for example, open a command window (DOS box)
and type:
C:/Python24/gerbmerge.bat
For more details on installation or running GerbMerge, see the
URL below.
Platform: all

147
doc/autosearch.html Normal file
View File

@ -0,0 +1,147 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>GerbMerge -- A Gerber-file merging program -- Automatic Placement</TITLE>
</HEAD>
<BODY BGCOLOR="#ffffff" LINK="#0000c0" VLINK="#8f008f">
<!-- -->
<P><FONT SIZE="+2">GerbMerge -- Automatic Placement</FONT></P>
<BLOCKQUOTE>
<P><A HREF="http://ruggedcircuits.com/gerbmerge"><FONT
SIZE="-1">Rugged Circuits LLC</FONT></A><FONT SIZE="-1"></FONT></P>
</BLOCKQUOTE>
<P><HR ALIGN=LEFT><TABLE WIDTH="100%" BORDER="0" CELLSPACING="2"
CELLPADDING="0">
<TR>
<TD><A HREF="index.html">Top-Level</A> | <A HREF="cfgfile.html">The Configuration File</A> | <A HREF="layoutfile.html">The Layout File</A> | Automatic Placement</TD>
</TR>
<TR>
<TD>&nbsp;</TD>
</TR>
<TR>
<TD>
<FONT SIZE="-1"><A HREF="#Overview">Introduction</A> | <A HREF="#Random">Randomized Search</A> |
<A HREF="#Exhaustive">Exhaustive Search</A> | <A HREF="#Repeats">Multiple Instances</A> | <A HREF="#Usage">Usage Notes</A></TD>
</TR>
</TABLE><HR ALIGN=LEFT></P>
<A NAME="Overview"><H2>Introduction</H2></A>
As an alternative to manual placement, either using the <A HREF="layoutfile.html">layout file</A>
approach or using the <TT>--place-file</TT> command-line option, GerbMerge can automatically
try to find the best arrangement of jobs on a panel that minimizes the total panel area.
Using automatic placement can save you time since you don't have to construct and experiment
with a layout file. The tradeoff, however, is that automatic placement may take a long time
to execute, and for panels with many, small jobs, the run time may be prohibitive. On the
other hand, experience suggests that good results can be obtained in just a few minutes,
even when GerbMerge is not allowed to search all possibilities.
<A NAME="Random"><H2>Randomized Search</H2></A>
<H3>The Basics</H3>
The randomized search approach has GerbMerge repeatedly place jobs randomly on a panel, possibly
rotated. After each placement, GerbMerge evaluates the total area of the panel, and if
it's less than the smallest area encountered so far, the placement is memorized as the
best so far.
<P>This may not sound like a very efficient approach but experience shows that it can
lead to nearly-optimal results fairly quickly. The reason is that although there can
be a huge number of possible placements for a given set of jobs, many of them are
equivalent with respect to total panel area.
<P>The randomized search approach is the default automatic placement method. It is
invoked simply by not specifying any layout file:
<PRE><CENTER>gerbmerge file.cfg</CENTER></PRE>
<P>The <A HREF="cfgfile.html">configuration file</A> must still be specified, of course.
After GerbMerge starts, you may press Ctrl-C at any
time to stop the process. In fact, you must press Ctrl-C at some point as GerbMerge
will try random placements forever.
<P>The best layout found when Ctrl-C is pressed will be used for panelization.
Note that the layout is also saved in the file specified by the
<TT>Placement</TT> assignment in the <TT>[MergeOutputFiles]</TT> section of the
<A HREF="cfgfile.html">configuration file</A>. Thus, if you want to experiment,
you can run different trials, save the best placements from each, then use the
best one by using the saved placement file as the input to
GerbMerge with the <TT>--place-file</TT> option.
<H3>Random+Exhaustive</H3>
The default operation of GerbMerge is to actually perform a hybrid search,
using both random search and exhaustive search. By default, GerbMerge will take
a list of N jobs and randomly place N-2 of them (randomly chosen). Then,
GerbMerge will exhaustively try to place the remaining 2 jobs on the panel to
minimize the area. This approach has been found to improve panel usage at minimal
cost since an exhaustive search of only 2 jobs is very quick.
<P>You can change the number of jobs to exhaustively search for a given random
placement with the <TT>--rs-fsjobs</TT> command-line option. For example,
<PRE><CENTER>gerbmerge --rs-fsjobs=2 file.cfg</CENTER></PRE>
The above example is the default behavior, i.e., exhaustively place 2 jobs and
randomly place N-2 jobs. By using a number higher than 2, there is less randomness
but fewer starting placements are tested per second.
<A NAME="Exhaustive"><H2>Exhaustive Search</H2></A>
The exhaustive search approach has GerbMerge try all possible placements for a given
set of jobs, one by one. This sounds like it may be an exponentially long approach,
and it is. For anything other than a few boards (less than 5 or so), exhaustive search
is prohibitive.
<P>The exhaustive search mode is invoked as follows:
<PRE><CENTER>gerbmerge --full-search file.cfg</CENTER></PRE>
You can stop the search at any time by pressing Ctrl-C. The best placement found so far
will be used for panelization and saved in the placement file specified by the <TT>Placement</TT> value in the
<TT>[MergeOutputFiles]</TT> section of the <A HREF="cfgfile.html">configuration file</A>.
<A NAME="Repeats"><H2>Multiple Instances</H2></A>
There is no need to repeat sections of a job in the configuration file if you want
a job to appear multiple times on a panel. You can use the <TT>Repeat=N</TT> configuration
option to indicate that a particular job is to have N copies on a panel. For example:
<PRE> [irtx]
Prefix=%(projdir)s/IRTransmitter/irtx
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
*SolderMaskTop=%(prefix)s.stc
*SolderMaskBottom=%(prefix)s.sts
<B>Repeat=5</B>
</PRE>
This job specifies all the layers as usual, then the last line indicates that 5
such jobs are to appear on the final panel. They may appear in various positions
and states of rotation, however.
<A NAME="Usage"><H2>Usage Notes</H2></A>
<H3>Area Estimates</H3>
GerbMerge will estimate and display the maximum possible panel usage as a percentage.
This estimate is frequently too high, as GerbMerge simply takes the area of each
job, adds in the area required by inter-job spacing, then adds all of these areas
together. The amount of that area that is used by actual jobs (and not inter-job
spacing) represents the best possible area usage. This usage will clearly not be
achieved unless all jobs magically fit together perfectly.
<P>In summary, it is pointless to wait for a random search for hours to hit
an estimated area utilization of 91% because, unless the dimensions of all
boards line up just so, that utilization is not achievable.
<H3>Panel Width and Height</H3>
Note that the <TT>PanelWidth</TT> and <TT>PanelHeight</TT> options in the
<A HREF="cfgfile.html">configuration file</A> constrain the search process. GerbMerge will not allow
any placement, either by random search or exhaustive search, to exceed the
panel dimensions. You can, therefore, guide the search process by choosing a
panel size that is not too large, thus preventing highly-unlikely placements
(think all jobs in one row) from being considered.
<P>Similarly, by choosing panels that are slightly wider than taller, or vice
versa, different placements can be considered and may lead to different
results. Consider these two configuration file options as a source of
experimentation.
<H3>Time vs. Money</H3>
How long should you wait for the best possible area utilization? It depends...
how much is your time worth?
<P>If you've achieved 85% utilization for a 30 sq. in. board, what will you
save by waiting and hoping for 90% (i.e., 28.3 sq. in.)? Assuming 64 cents/sq. in.
(<A HREF="http://www.barebonespcb.com">BareBonesPCB.com</A> cost), you will save $1.09.
<HR ALIGN=LEFT>
<P><CENTER><FONT SIZE="-1">&COPY; 2003-2011, Copyright by <A HREF="http://ruggedcircuits.com">Rugged Circuits LLC</A>; All Rights Reserved. mailto: <A HREF="mailto:support@ruggedcircuits.com?subject=GerbMerge">support@ruggedcircuits.com</A></FONT></CENTER>
</BODY>
</HTML>

441
doc/cfgfile.html Normal file
View File

@ -0,0 +1,441 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>GerbMerge -- A Gerber-file merging program -- The Configuration File</TITLE>
</HEAD>
<BODY BGCOLOR="#ffffff" LINK="#0000c0" VLINK="#8f008f">
<!-- -->
<P><FONT SIZE="+2">GerbMerge -- The Configuration File</FONT></P>
<BLOCKQUOTE>
<P><A HREF="http://ruggedcircuits.com/gerbmerge"><FONT
SIZE="-1">Rugged Circuits LLC</FONT></A><FONT SIZE="-1"></FONT></P>
</BLOCKQUOTE>
<P><HR ALIGN=LEFT><TABLE WIDTH="100%" BORDER="0" CELLSPACING="2"
CELLPADDING="0">
<TR>
<TD><A HREF="index.html">Top-Level</A> | The Configuration File | <A HREF="layoutfile.html">The Layout File</A> | <A HREF="autosearch.html">Automatic Placement</A></TD>
</TR>
<TR>
<TD>&nbsp;</TD>
</TR>
<TR>
<TD>
<FONT SIZE="-1"><A HREF="#Parameters">Operating Parameters</A> | <A HREF="#Jobs">Job Descriptions</A> |
<A HREF="#Syntax">Syntax Notes</A></TD>
</TR>
</TABLE><HR ALIGN=LEFT></P>
<H2>Introduction</H2>
<P>The configuration file tells GerbMerge things like:
<UL>
<LI><P>How much space to leave between jobs in the final panel</LI>
<LI><P>Whether or not to draw cut lines or crop marks, and on which layers</LI>
<LI><P>Whether or not to generate a fabrication drawing</LI>
<LI><P>The job names and files to be panelized</LI>
<LI><P>The output file names</LI>
</UL>
<P>Note that the configuration file does not specify layout of jobs on the
panel. This layout is described by the <A HREF="layoutfile.html">layout file</A>.
The layout of jobs on the panel may also be constructed automatically using
the <A HREF="autosearch.html">automatic placement</A> mode of operation.
<P><H2>Help!</H2>
<P>The rest of this document has a lot of information and it's easy to get overwhelmed. Users of GerbMerge
complain that the configuration file is the biggest hurdle to overcome in using the program.
<P>Don't panic. Start with a <A HREF="layout2.cfg">sample configuration file</A> and
modify it for your own jobs. The comments in the sample file will guide you through the
process.
<P><A NAME="Syntax"></A>
<H2>Syntax Notes</H2>
<P>The configuration file is a plain text file that can be created with any
text editor. It is parsed using Python's
<A HREF="http://docs.python.org/lib/module-ConfigParser.html"><TT>ConfigParser</TT></A> module.
See the documentation for this module for a full description of supported syntax.
<P>Note that comments in this file begin with a <TT>'#'</TT> character.
Comments must occupy an entire line and must not have any characters before the
'#' character, including blanks. Comments cannot be placed at the end of a
line. For example:
<PRE>
# This is correct...a comment occupies the entire line
# Incorrect...comment preceded by blanks
PanelWidth = 10.5 # This is INCORRECT...a comment cannot be placed at the end of a line
</PRE>
<P>The configuration file has a standard "INI-style" syntax comprising:
<UL>
<LI><I>Sections</I> delimited by section names in square brackets (e.g., <TT>[Options]</TT>)
<LI><I>Assignments</I> of the form '<TT>Name = Value</TT>'
</UL>
<P>The configuration file parser supports variable substitution. You can specify a common
pathname prefix, for example, and substitute it in subsequent assignments, like this:
<PRE>
[CPUBoard]
Prefix = /home/user/eagle/cpuboard
# Note the syntax '%(prefix)s' is a variable string substitution.
# Even though we said 'Prefix = ...' we use LOWERCASE 'prefix' in the actual substitution!
BoardOutline = %(prefix)s/cpu.bor
Drills = %(prefix)s/cpu.xln
</PRE>
<HR>
<P><B>NOTE:</B> the parser converts all names you assign to into <B>lowercase letters only</B>.
<HR>
<P>In general, assignments are local to the section in which they reside, i.e.,
the names assigned to are not visible in the other sections. However, any
assignments in the section named <TT>[DEFAULT]</TT> are visible in all
sections. For example:
<PRE>
[DEFAULT]
EagleDir = /home/user/eagle
[CPUBoard]
Prefix = %(eagledir)s/cpuboard
BoardOutline = %(prefix)s/cpu.bor
Drills = %(prefix)s/cpu.xln
[IOBoard]
Prefix = %(eagledir)s/ioboard
BoardOutline = %(prefix)s/io.bor
Drills = %(prefix)s/io.xln
</PRE>
<P>Have a look at the sample configuration files <A HREF="layout1.cfg"><TT>layout1.cfg</TT></A>
and <A HREF="layout2.cfg"><TT>layout2.cfg</TT></A> for a quick overview of this file's syntax.
<P><A NAME="Parameters"></A>
<H2>Operating Parameters</H2>
<P>The first section of the configuration file is called <TT>[Options]</TT>.
This section specifies operating parameters for the job.
<P>The following optional parameters are supported:
<DL>
<A NAME="ToolList"><DT><B>Tool List</B></DT></A>
<DD><TT>ToolList = /home/user/eagle/toollist.drl</TT>
<P><B>NOTE:</B>If you're using a recent version of Eagle or other modern PCB program, you can probably
ignore this option. Try commenting it out and see what happens!
<P>This parameter sets the default tool list (or &quot;drill rack&quot;) in effect for jobs that (a) do not have
embedded tool sizes in the Excellon file, and (b) do not have a tool list specified
as part of the job description (see below).
<P>As of Eagle version 4.11r2, tool sizes are embedded in the Excellon file, like this:
<PRE>
...
T01C0.032
T02C0.045
T03C0.115
...
</PRE>
<P>For Excellon files with embedded tool sizes, no tool list file need be specified. Otherwise,
a tool list file must be specified that contains something like:
<PRE>
T01 0.032in
T02 0.045in
T03 0.115in
</PRE>
<P>Suffixes of 'mm' and 'mil' may be used instead of 'in' to indicate millimetres and mils.
<P>Note that Eagle's CAM Processor uses two different forms of Excellon output
devices, <TT>EXCELLON</TT> in which tool sizes are embedded in the drill file,
and <TT>EXCELLON_RACK</TT> which requires an external tool list file, or drill rack. The
latter may be desirable when you want to send your boards to a manufacturer
with a limited set of drill sizes, or that charges by the number of different
drill sizes used. In this case, do specify the <TT>ToolList</TT> option and
set it to the drill rack you specified for the <TT>EXCELLON_RACK</TT> device.
</DD>
<A NAME="ExcellonDecimals"><DT><B>Excellon Decimals</B></DT></A>
<DD><TT>ExcellonDecimals = 4</TT>
<P>This optional setting specifies the number of digits after the decimal point in the input
Excellon drill files. These files contain (X,Y) drill locations specified as integers, but
represent actual positions in the format <TT>M.N</TT> where there are <TT>M</TT> digits
before the decimal point and <TT>N</TT> digits after. The default number of decimal digits
is 4, hence a 2.4 integer format, so that the drill instruction <TT>X12300Y9400</TT> means to drill at (1.23", 0.94").
In 2.3 format (with <TT>ExcellonDecimals=3</TT>) the above would appear as
<TT>X1230Y940</TT>. Note that it is assumed that leading 0's are omitted, or else no zeroes are omitted.
Omitted trailing 0's are not yet supported.
<P>As of this writing, older Eagle versions use a 2.3 format (prior to version 4.11r12), more recent Eagle versions use a 2.4 format, while Orcad and PCB use 2.4.</DD>
<P>Note that each job may have its own <TT>ExcellonDecimals</TT> setting (see <A HREF="#LocalExcellonDecimals">below</A>) to override this global
setting.
<P>Finally, note that the <TT>ExcellonDecimals</TT> option applies to the expected format for
the <B>input</B> Excellon files, i.e., the drill files that GerbMerge reads in. The <A HREF="#ExcellonLeadingZeros">
<TT>ExcellonLeadingZeros</TT></A> option below applies to the <B>output</B> Excellon file generated by GerbMerge.
<A NAME="CutLineLayers"><DT><B>Cut Line Layers</B></DT></A>
<DD><TT>CutLineLayers = *toplayer,*bottomlayer</TT>
<P>This parameter indicates which, if any, layers are to have cut lines drawn on them. Cut lines
define the rectangular extents of each individual job on the panel. They are intended to help you
in cutting out the individual jobs from the panel.
<P>The value of this parameter is a list of layer names, which are defined for each job (see below).
Layer names may be separated with commas or semicolons.
<P>Note that <B>layer names must be written in lowercase letters</B>, even if they are defined with
uppercase letters. Also note that all layer names except the board outline layer will begin
with an asterisk '<B>*</B>'.</DD>
<P>This parameter may be omitted, or be set to <TT>None</TT> to indicate that no cut lines should
be drawn.</DD>
<A NAME="CropMarkLayers"><DT><B>Crop Mark Layers</B></DT></A>
<DD><TT>CropMarkLayers = *toplayer,*bottomlayer</TT>
<P>This parameter indicates which, if any, layers are to have crop marks drawn on them. Crop marks
are small L-shaped marks at the four corners of the final panel. Some board manufacturers require
crop marks to ensure registration and to unambiguously define the extents of the job.
<P>The value of this parameter is a list of layer names, which are defined for each job (see below).
Layer names may be separated with commas or semicolons.
<P>Note that <B>layer names must be written in lowercase letters</B>, even if they are defined with
uppercase letters. Also note that all layer names except the board outline layer will begin
with an asterisk '<B>*</B>'.
<P>This parameter may be omitted, or be set to <TT>None</TT> to indicate that no crop marks should
be drawn.</DD>
<A NAME="FabricationDrawingFile"><DT><B>Fabrication Drawing File</B></DT></A>
<DD><TT>FabricationDrawingFile = fabdwg.ger</TT>
<P>This optional parameter may be set to a filename, to 'none', or omitted
entirely. When a valid filename is specified, GerbMerge will generate a Gerber
RS274X file containing a fabrication drawing for the entire project. The
drawing contains a box for the outline of the entire panel, dimension arrows
for the panel, drill symbols for each drill hit, a drill tool legend, and
optional user text. Some board manufacturers require a fabrication drawing.
<P>The Fabrication Drawing Text option below allows you add user-defined text
to this drawing.
<P>Note that for generating a fabrication drawing, no more
than 26 drill tools can appear in the merged output.</DD>
<A NAME="FabricationDrawingText"><DT><B>Fabrication Drawing Text</B></DT></A>
<DD><TT>FabricationDrawingText = project/fabdwg.txt</TT>
<P>This optional parameter may specify the name of a file containing plain text. Each line
in the file is added to the fabrication drawing, if one is enabled.</DD>
<A NAME="ExcellonLeadingZeros"><DT><B>ExcellonLeadingZeros</B></DT></A>
<DD><TT>ExcellonLeadingZeros = 0</TT>
<P>This optional setting creates a merged Excellon output file with leading
zeros. The default is to use leading-zero suppression. For example, with leading-zero
suppression, a drill hit at location (1.23",4.56") would be written in the output Excellon file as:
<PRE> X12300Y45600
</PRE>Without leading-zero suppression (i.e., with <TT>ExcellonLeadingZeros=1</TT>) it would appear as:
<PRE> X012300Y045600
</PRE><P>Not using leading-zero suppression may make it easier for some
Gerber viewers to properly interpret the Excellon file. Try setting
<TT>ExcellonLeadingZeros=1</TT> if your drills appear in completely the
wrong locations when viewing your merged output files in a Gerber
viewer.</DD>
<P>Finally, note that the <TT>ExcellonLeadingZeros</TT> option applies to the format for
the <B>output</B> Excellon file as generated by GerbMerge. The <A HREF="#ExcellonDecimals">
<TT>ExcellonDecimals</TT></A> option described above applies to the <B>input</B> Excellon files read
in by GerbMerge.
<A NAME="OutlineLayerFile"><DT><B>Outline Layer File</B></DT></A>
<DD><TT>OutlineLayerFile = project.oln</TT>
<P>This optional parameter indicates that an additional output file (Gerber layer) is to
be generated containing a rectangle that is drawn around the edges of the final panelized
job. The value of this parameter is the name of the file. If 'none' is specified or this option
is omitted, no outline file is generated.
<P>This outline layer is useful in circuit board milling
for defining the path extents of a contour router bit so that the entire panel may be cut out by
the mill.</DD>
<A NAME="ScoringFile"><DT><B>Scoring File</B></DT></A>
<DD><TT>ScoringFile = project.sco</TT>
<P>This optional parameter indicates that an additional output file (Gerber layer) is to
be generated containing scoring lines. These scoring lines describe the path for a scoring
tool to make V-grooves in the board in between jobs, so that the jobs may be easily snapped
apart. The value of this parameter is the name of the file. If 'none' is specified or this option
is omitted, no scoring file is generated.</DD>
<A NAME="PanelWidthHeight"><DT><B>Panel Width/Height</B></DT></A>
<DD><TT>PanelWidth = 12.6</TT><BR>
<TT>PanelHeight = 7.8</TT>
<P>These parameters (in inches) set the dimensions of the board manufacturer's panels. An error message
will be displayed if the panelized job exceeds these dimensions. You can change these settings
to match the panel size of your board manufacturer, if you know it.
<P>For <A HREF="autosearch.html">automatic placement</A>, the panel size defined by these settings
constraint the random placements such that only placements that would fit on the panel are considered.</DD>
<A NAME="Margins"><DT><B>Margins</B></DT></A>
<DD><TT>LeftMargin = 0.1</TT><BR>
<TT>RightMargin = 0.1</TT><BR>
<TT>TopMargin = 0.1</TT><BR>
<TT>BottomMargin = 0.1</TT>
<P>These four parameters set the amount of extra space to leave around the
edges of the panel to simplify tooling and handling. These margins are
specified in inches, and default to 0" if not specified. These spacings will
only be visible to the board manufacturer if you enable crop marks (see
<A HREF="#CropMarkLayers">CropMarkLayers</A> above) or use an <A HREF="#OutlineLayerFile">outline layer</A>.
<A NAME="JobSpacing"><DT><B>Job Spacing</B></DT></A>
<DD><TT>XSpacing = 0.125</TT><BR>
<TT>YSpacing = 0.125</TT>
<P>These parameters set the job-to-job spacing in horizontal (X) and vertical (Y) directions.
The default spacing is 0.125 inches if these parameters are not specified. Normally, both
parameters will have the same value, but different values can be used to &quot;tweak&quot;
a panel to exactly fit some dimensions.</DD>
<A NAME="CutLineWidth"><DT><B>Cut Line Width</B></DT></A>
<DD><TT>CutLineWidth = 0.01</TT>
<P>This optional parameter (in inches) indicates the width of the line used to draw cut lines. If not
specified, the default is 0.01".</DD>
<A NAME="CropMarkWidth"><DT><B>Crop Mark Width</B></DT></A>
<DD><TT>CropMarkWidth = 0.01</TT>
<P>This optional parameter (in inches) indicates the width of the line used to draw crop marks. If not
specified, the default is 0.01".</DD>
<A NAME="AllowMissingLayers"><DT><B>Allow Missing Layers</B></DT></A>
<DD><TT>AllowMissingLayers = 0</TT>
<P>This parameter may be set to either 0 or 1. When set to 0, all jobs must have the same
layer names. This is the most common case. This parameter guards against misspelling of
layer names and having them mistakenly placed on a different layer.
<P>Some jobs, however, will have fewer or more layers. For example, mixing jobs that do
and do not have surface-mount components may mean that some jobs will have solder mask layers
and some will not. Setting <TT>AllowMissingLayers</TT> to 1 allows you to panelize such
job mixtures. Take care, however, to <A HREF="index.html#Verifying">inspect the output carefully</A>
in this case to catch layer-name surprises.</DD>
<A NAME="DrillClusterTolerance"><DT><B>DrillClusterTolerance</B></DT></A>
<DD><TT>DrillClusterTolerance = 0</TT>
<P>This option is intended to reduce the number of drills in the output by
eliminating drill sizes that are too close to make a difference. For example,
it probably does not make sense to have two separate 0.031" and 0.0315" drills.
The <TT>DrillClusterTolerance</TT> value specifies how much tolerance is allowed in
drill sizes, in units of inches. Multiple drill tools that span twice this
tolerance will be clustered into a single drill tool. For example, a set of
0.031", 0.0315", 0.032", and 0.034" drills will all be replaced by a single
drill tool of diameter (0.031"+0.034")/2 = 0.0325". It is guaranteed that all
original drill sizes will be no farther than <TT>DrillClusterTolerance</TT> from the
drill tool size generated by clustering.
<P>Setting <TT>DrillClusterTolerance</TT> to 0 (the default) disables clustering.</DD>
<A NAME="MinimumFeatureSize"><DT><B>MinimumFeatureSize</B></DT></A>
<DD><TT>MinimumFeatureSize = None</TT>
<P>Use this option to automatically thicken features on particular layers. This is
intended for thickening silkscreen to some minimum width. The value of this
option must be a comma-separated list of layer names followed by minimum
feature sizes (in inches) for that layer. Comment this out to disable
thickening. Example usage is:
<PRE> MinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008</PRE></DD>
<A NAME="FiducialPoints"><DT><B>FiducialPoints</B></DT></A>
<DD><TT>FiducialPoints = None</TT>
<P>Use this option to automatically add fiducials (little round markers used to aid
in automatic component placement) to your final panel. This makes the most sense when
you leave some <A HREF="#Margins">margins</A> around your panel and place the fiducials on the margins.
<P>The parameter to this option is a list of X,Y points at which to draw fiducials, relative to the
edges of the final panel. Positive values are relative to the lower-left, while negative values are
relative to the upper-right. For example:
<PRE> FiducialPoints = 0.125,0.125,-0.1,-0.1</PRE>
would place one fiducial at (0.125,0.125) relative to the lower-left, and another fiducial
a distance of (0.1,0.1) from the top-right of the final panel. To place a fiducial at the top-left
of the final panel:
<PRE> FiducialPoints = 0.125,-0.125</PRE>
The <A HREF="#FiducialCopperDiameter"><TT>FiducialCopperDiameter</TT></A> and <A HREF="#FiducialMaskDiameter"><TT>FiducialMaskDiameter</TT></A> options control the appearance of the fiducials.</DD>
<P>
<A NAME="FiducialCopperDiameter"><DT><B>FiducialCopperDiameter</B></DT></A>
<DD><TT>FiducialCopperDiameter = 0.08</TT>
<P>This option sets the diameter of fiducials in inches. See the <A HREF="#FiducialPoints"><TT>FiducialPoints</TT></A> configuration option for more information.</DD>
<A NAME="FiducialMaskDiameter"><DT><B>FiducialMaskDiameter</B></DT></A>
<DD><TT>FiducialMaskDiameter = 0.32</TT>
<P>This option sets the diameter of the soldermask opening around fiducials in inches. See the <A HREF="#FiducialPoints"><TT>FiducialPoints</TT></A> configuration option for more information.</DD>
</DL>
<P><A NAME="Jobs"></A>
<H2>Job Descriptions</H2>
<P>Each input job is described in its own section. The job is described by providing file names for
each layer. Layer names are up to you, but note the following:
<UL>
<LI><P>Layer names may be specified with lowercase and uppercase letters, but are converted to
all-lowercase by GerbMerge. Note that this applies to layer names, not filenames.
<LI><P>Each job must have at least a 'boardoutline' and 'drills' layer, specifying the Gerber
board outline and Excellon drills layer, respectively.
<LI><P>Each job may have an optional 'toollist' file specifying the tool list (or &quot;drill rack&quot;)
in effect for this job only. This setting overrides the global <TT>ToolList</TT> option, described above.
If the Excellon file for the job has embedded tool sizes, this option is ignored.
<LI><P>All layer names other than 'boardoutline' and 'drills' must begin with an asterisk '*'
character.
</UL>
<P>Consider the following example:
<PRE>
[CPUBoard]
BoardOutline = /home/user/eagle/cpuboard/cpu.bor
Drills = /home/user/eagle/cpuboard/cpu.xln
ToolList = /home/user/eagle/cpuboard/tools.drl
*TopLayer = /home/user/eagle/cpuboard/cpu.cmp
*BottomLayer = /home/user/eagle/cpuboard/cpu.sol
*Silkscreen = /home/user/eagle/cpuboard/cpu.plc
</PRE>
<P>Job names (in square brackets) are fairly arbitrary and need not correspond to any file names. They must,
however, comprise only letters, digits, and the underscore character. Furthermore, job names must begin with
a letter. Job names, unlike layer names, are case sensitive.
<P>Each assignment statement assigns a file name to a layer name. As mentioned above,
the layer names 'boardoutline' and 'drills' are reserved and required. The optional 'toollist' layer
is not an actual layer but an assignment that indicates the tool list in effect for this job. All other layer
names are up to you and must begin with an asterisk '*'.
<P>Make good use of variable substitutions (see the sample <A HREF="layout1.cfg"><TT>layout1.cfg</TT></A>
and <A HREF="layout2.cfg"><TT>layout2.cfg</TT></A> files) to avoid
typing the same pathname over and over.
<P>In addition to specifying board layers, each job description can also have job-specific
parameter assignments:
<DL>
<DT><B>Repeat</B></DT>
<DD><TT>Repeat = 3</TT>
<P>This option is only used for <A HREF="autosearch.html">automatic placement</A> and indicates the number
of times this job is to appear in the final panel. For manual placement, this option is ignored. This option
may be left unspecified in which case a repeat count of 1 is assumed.
<A NAME="LocalExcellonDecimals"><DT><B>ExcellonDecimals</B></DT></A>
<DD><TT>ExcellonDecimals = 3</TT>
<P>This option overrides the global <A HREF="#ExcellonDecimals"><TT>ExcellonDecimals</TT></A> setting in the
<TT>[Options]</TT> section for this job only. This allows jobs with different
Excellon decimal formats to be panelized. This option may be left unspecified
in which case the global <TT>ExcellonDecimals</TT> setting is applied.
</DL>
<P><A NAME="MergeOutputFiles"></A>
<H2>Merge Output Files</H2>
<P>GerbMerge combines data from multiple jobs grouped by layer. All of the "bottom copper"
layers from all jobs, for example, will be combined into a single "bottom copper" file.
The names of these combined output files can be set in the <TT>[MergeOutputFiles]</TT> section of the
configuration file.
<P>This section contains assignments of file names to layer names. The layer names must be the
same as the ones specified in the <A HREF="#Jobs"><TT>[Jobs]</TT></A> section of the configuration file.
All layer names must begin with an asterisk '<B>*</B>' except for the following four reserved layer names:
<UL><LI><TT>BoardOutline</TT></LI>
<LI><TT>Drills</TT></LI>
<LI><TT>Placement</TT></LI>
<LI><TT>ToolList</TT></LI>
</UL>
<P>The first two reserved layer names are actual layers, while
<TT>Placement</TT> refers to the placement file generated by GerbMerge
containing positions of jobs on the final panel, and <TT>ToolList</TT> refers
to the combined tool list file generated by GerbMerge.
<P> Any assignment made in this section that does not begin with an asterisk or is not an assignment
to one of the above four reserved names is considered a general variable assignment for future
string substitution.
<P>Here is an example:
<PRE>
[MergeOutputFiles]
Prefix = job1
BoardOutline = %(prefix)s.bor
Drills = %(prefix)s.xln
*topcopper = %(prefix)s.cmp
*bottomcopper = %(prefix)s.sol
</PRE>
<P>If an assignment to a layer name is missing, GerbMerge will create the file <TT>merged.layername.ger</TT> where
'<TT>layername</TT>' is the layer name. Default values for the four reserved names are <TT>merged.boardoutline.ger</TT>
for the <TT>BoardOutline</TT> layer, <TT>merged.drills.xln</TT> for the <TT>Drills</TT> layer, <TT>merged.placement.txt</TT> for the <TT>Placement</TT> file, and <TT>merged.toollist.drl</TT> for the <TT>ToolList</TT> combined tool list file.
<HR ALIGN=LEFT>
<P><CENTER><FONT SIZE="-1">&COPY; 2003-2011, Copyright by <A HREF="http://ruggedcircuits.com">Rugged Circuits LLC</A>; All Rights Reserved. mailto: <A HREF="mailto:support@ruggedcircuits.com?subject=GerbMerge">support@ruggedcircuits.com</A></FONT></CENTER>
</BODY>
</HTML>

BIN
doc/ex1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
doc/ex1a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
doc/ex1b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
doc/ex1c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
doc/ex1d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
doc/ex1e.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
doc/ex1f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
doc/ex1g.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

696
doc/gpl.html Normal file
View File

@ -0,0 +1,696 @@
<HTML>
<HEAD>
<META NAME="GENERATOR" CONTENT="Adobe PageMill 3.0 Win">
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>GerbMerge -- License</TITLE>
</HEAD>
<BODY BGCOLOR="#ffffff" LINK="#0000c0" VLINK="#8f008f">
<!-- -->
<P><FONT SIZE="+2">GerbMerge -- A Gerber-file merging program</FONT></P>
<BLOCKQUOTE>
<P><A HREF="http://ruggedcircuits.com/gerbmerge"><FONT
SIZE="-1">Rugged Circuits LLC</FONT></A><FONT SIZE="-1"></FONT></P>
</BLOCKQUOTE>
<P><HR ALIGN="LEFT"></P>
<PRE>
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
</PRE>
<HR ALIGN=LEFT>
<P><CENTER><FONT SIZE="-1">&COPY; 2003-2011, Copyright by <A HREF="http://ruggedcircuits.com">Rugged Circuits LLC</A>; All Rights Reserved. mailto: <A HREF="mailto:support@ruggedcircuits.com?subject=GerbMerge">support@ruggedcircuits.com</A></FONT></CENTER>
</BODY>
</HTML>

392
doc/index.html Normal file
View File

@ -0,0 +1,392 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<META HTTP-EQUIV="Description" CONTENT="
BEGIN PYTHON-PACKAGE-INFO 1.0
Current-Version: 1.8
Title: GerbMerge -- A Gerber-file merging program
Home-page: http://ruggedcircuits.com/gerbmerge
Description: Merges Gerber/Excellon circuit board files
Keywords: Eagle Gerber Excellon merging panel panelizing merge PCB
Author: Rugged Circuits LLC, mailto:support@ruggedcircuits.com
Maintained-by: The author
Primary-site: http://ruggedcircuits.com/gerbmerge
Alternate-site: None
Original-site: Same as primary site
Platform: Unix/Windows/Mac
Copying-policy: Free software, see copyright info below for details
Difficulty-rating: Some installation required
System-requirements: None
Software-requirements: Python 2.4, eGenix mxBase 2.0.4, SimpleParse 2.0.0
This program is useful for designers of electronic printed circuit boards. The
program takes CAM data (Gerber RS274-X and Excellon format) output files from
multiple jobs and combines them (i.e., panelizes) into a single job. Intended
primarily for the Eagle CAD program, other CAD programs such as Orcad and PCB
have been known to work.
END PYTHON-PACKAGE-INFO
">
<TITLE>GerbMerge -- A Gerber-file merging program</TITLE>
</HEAD>
<BODY BGCOLOR="#ffffff" LINK="#0000c0" VLINK="#8f008f">
<!-- -->
<P><FONT SIZE="+2">GerbMerge -- A Gerber-file merging program</FONT></P>
<BLOCKQUOTE>
<P><A HREF="http://ruggedcircuits.com/gerbmerge"><FONT
SIZE="-1">Rugged Circuits LLC</FONT></A><FONT SIZE="-1"></FONT></P>
</BLOCKQUOTE>
<P><HR ALIGN=LEFT><TABLE WIDTH="100%" BORDER="0" CELLSPACING="2"
CELLPADDING="0">
<TR>
<TD>Top-Level | <A HREF="cfgfile.html">The Configuration File</A> | <A HREF="layoutfile.html">The Layout File</A> | <A HREF="autosearch.html">Automatic Placement</A></TD>
<TD ALIGN="RIGHT"><FONT SIZE="-1">Version 1.8</FONT></TD>
</TR>
<TR>
<TD><!-- --></TD>
<TD ALIGN="RIGHT"><FONT SIZE="-1">June 8, 2011</FONT></TD>
</TR>
<TR>
<TD>
<FONT SIZE="-1"><A HREF="#Requirements">Requirements</A> | <A HREF="#Installation">Installation</A> |
<A HREF="#Running">Running GerbMerge</A> | <A HREF="#Verifying">Verifying the Output</A> | <A HREF="#Limitations">Limitations</A> | <A HREF="#ProgramOptions">Program Options</A> | <A HREF="#Copyright">Copyright</A> | <A HREF="#Todo">To
Do</A> | <A HREF="#Credits">Credits</A> | <A HREF="#History">History</A></FONT></TD>
<TD><!-- --></TD>
</TR>
</TABLE><HR ALIGN=LEFT></P>
<H2>What's New</H2>
<P>In release 1.8:
<UL>
<LI>Released under more recent GPL v3 license</LI>
<LI>Summary statistics prints out smallest drill tool diameter</LI>
<LI>Added <A HREF="cfgfile.html#FiducialPoints"><TT>FiducialPoints</TT></A>, <A HREF="cfgfile.html#FiducialCopperDiameter"><TT>FiducialCopperDiameter</TT></A>, and <A HREF="cfgfile.html#FiducialMaskDiameter"><TT>FiducialMaskDiameter</TT></A> configuration options</LI>
<LI>Added option to write fiducials to final panel</LI>
<LI>Scoring lines now go all the way across a panel</LI>
</UL>
<P>In release 1.7:
<UL>
<LI>Added a new command-line option <TT>--search-timeout</TT> to time-limit the automatic placement process.</LI>
<LI>Added preliminary support for a GUI controller interface.</LI>
</UL>
<P><A NAME="Introduction"></A></P>
<H2>Introduction</H2>
<P>GerbMerge is a program for combining (panelizing) the CAM data from multiple printed
circuit board designs into a single set of CAM files. The purpose of
doing so is to submit a single job to a board manufacturer, thereby saving on manufacturing costs.
<P>GerbMerge currently works with:
<UL>
<LI>CAM data generated by the <A HREF="http://www.cadsoft.de">Eagle</A> circuit board
design program, with &quot;best effort&quot; support for Orcad, Protel, and <A HREF="http://www.sourceforge.net/projects/pcb">PCB</A></LI>
<LI>Artwork in Gerber RS274-X format</LI>
<LI>Drill files in Excellon format</LI>
</UL>
Here is <A HREF="sample.jpg">one sample</A> and <A HREF="sample2.jpg">another sample</A> of the program's output. These samples
demonstrate panelizing multiple, different jobs, and also demonstrate board rotation.
<P><A NAME="Download"></A></P>
<H2>Download</H2>
<H3>Version 1.8</H3>
<UL>
<LI><A HREF="gerbmerge-1.8.win32.exe">Windows installer</A></LI>
<LI><A HREF="gerbmerge-1.8.tar.gz">Source</A></LI>
</UL>
<P><A NAME="Requirements"></A></P>
<H2>Requirements</H2>
<P>GerbMerge is written in pure <A HREF="http://www.python.org">Python</A>. It
depends upon the following packages for operation:
<UL>
<LI><A HREF="http://www.python.org">Python</A> version 2.4 or later</LI>
<LI><A HREF="http://simpleparse.sourceforge.net">SimpleParse</A> version 2.1.0 or later</LI>
</UL>
<P>All of the above packages come with easy installation programs for both Windows, Mac OS X,
and Linux.
<P><A NAME="Installation"></A></P>
<H2>Installation</H2>
<P>First, install all of the packages listed above in the Requirements section.
<H3>Windows</H3>
<P>Run the <TT>gerbmerge1.8.exe</TT> installation program. I will assume
you choose all of the default installation options. The installer
will create and populate the following directories:</P>
<UL>
<PRE>
c:\Python24\lib\site-packages\gerbmerge
c:\Python24\gerbmerge
</PRE>
</UL>
<P>The above assumes you have Python installed in <TT>C:\Python24</TT>. The
first directory is where the actual program resides. The second directory
contains the documentation, example files, etc. In the <TT>C:\Python24</TT>
directory is a sample batch file <TT>GERBMERGE.BAT</TT> which shows you how to
run the GerbMerge program.
<H3>Unix / Mac OS X</H3>
<P>Extract the <TT>gerbmerge1.8.tar.gz</TT> file then install as follows:</P>
<UL>
<TT>python setup.py install</TT>&nbsp;&nbsp;&nbsp;(You may need to be root to install to system directories)
</UL>
<P>The installer will create and populate the following directories/files:</P>
<UL>
<PRE>
/usr/local/lib/python2.4/site-packages/gerbmerge
/usr/local/lib/python2.4/gerbmerge
/usr/local/bin/gerbmerge
</PRE>
</UL>
<P>The above assumes your Python library directory is as indicated (it may be
elsewhere but the installer should be able to find it, so don't worry about
it). The first directory is where the actual program resides. The second
directory contains the documentation, example files, etc. A sample program for
invoking GerbMerge is installed as <TT>/usr/local/bin/gerbmerge</TT>...feel free to move
it somewhere else.
<P>Not all Linux distributions are the same, however. If you have trouble, there is a useful set of instructions from <A HREF="http://blog.bhargavaz.us/2009/05/installing-gerbmerge-on-ubuntu-linux.html">Chetan Bhargava</A> for installing GerbMerge on Ubuntu distributions.
<P><A NAME="Running"></A></P>
<H2>Running GerbMerge</H2>
<H3>Windows</H3>
<P>Open a DOS box and invoke the Python interpreter on the <TT>gerbmerge.py</TT> file.
Have a look at GERBMERGE.BAT (and put this on your Path somewhere) for an example.
<PRE><CENTER>c:\python24\python c:\python24\lib\site-packages\gerbmerge\gerbmerge.py</CENTER></PRE>
<H3>Unix / Mac OS X</H3>
<P>You run GerbMerge by invoking the Python interpreter on the <TT>gerbmerge.py</TT>
file of the <TT>gerbmerge</TT> package. For example:</P>
<PRE><CENTER>python /usr/local/lib/python2.4/site-packages/gerbmerge/gerbmerge.py</CENTER></PRE>
<P>The <TT>gerbmerge</TT> shell script that comes with this software contains an
example for running GerbMerge, modelled on the above. By default, this shell
script is installed in <TT>/usr/local/bin</TT> so you should just be able
to type <TT>gerbmerge</TT> from a shell prompt.
<H3>Operation</H3>
There are three ways to run GerbMerge:
<OL><LI>By manually specifying the relative placement of jobs</LI>
<LI>By manually specifying the absolute placement of jobs</LI>
<LI>By letting GerbMerge automatically search for a placement that minimizes total panel area</LI>
</OL>
<H4>Manual Relative Placement</H4>
For the manual relative placement approach, GerbMerge needs two input text files:
<UL>
<LI><P>The <I>configuration file</I> specifies global options and defines the jobs
to be panelized</LI>
<LI><P>The <I>layout file</I> specifies how the jobs are to be laid out.</LI>
</UL>
<P>The names of these files are the two required parameters to GerbMerge:
<PRE><CENTER>gerbmerge file.cfg file.def</CENTER></PRE>
<P>The following links describe the contents of the <A HREF="cfgfile.html">configuration
file</A> and <A HREF="layoutfile.html">layout file</A>.
<H4>Manual Absolute Placement</H4>
<P>For the manual absolute placement approach, GerbMerge also needs the configuration file
as well as another text file that specifies where each job is located on the panel and
whether or not it is rotated:
<PRE><CENTER>gerbmerge --place-file=place.txt file.cfg</CENTER></PRE>
<P>The <TT>place.txt</TT> file looks something like:
<PRE>job1 0.100 0.100
cpu 0.756 0.100
cpu*rotated 1.35 1.50
</PRE>
<P>This method of placement is not meant for normal use. It can be used to recreate
a previous invocation of GerbMerge, since GerbMerge saves its results in a text file
(whose name is set in the <A HREF="cfgfile.html#MergeOutputFiles"><TT>[MergeOutputFiles]</TT></A>
section of the configuration file) after every run. Thus, you can experiment with
different parameters, save a placement you like, do some more experimentation, then return
to the saved placement if necessary.
<P>Alternatively, this method of placement can be used with third-party back ends that
implement intelligent auto-placement algorithms, using GerbMerge only for doing the
actual panelization.
<H4>Automatic Placement</H4>
<P>For the <A HREF="autosearch.html">automatic placement</A> approach, GerbMerge only needs the configuration file:
<PRE><CENTER>gerbmerge file.cfg</CENTER></PRE>
Command-line options can be used to modify the search algorithm. See the
<A HREF="autosearch.html">Automatic Placement</A> page for more information.
<H3>Input File Requirements</H3>
GerbMerge requires the following input CAM files:
<UL>
<LI><P>Each job must have a Gerber file describing the board outline, which is assumed
rectangular. In Eagle, a board outline is usually generated from the Dimension layer.
This board outline is a width-0 line describing the physical extents of the board. If you're
not using Eagle, you don't have to generate a width-0 rectangle, but GerbMerge does need
to use some Gerber layer to determine the extents of the board. GerbMerge will take the maximum
extents of all drawn objects in this layer as the extents of the board.</LI>
<LI><P>Each job must have an Excellon drill file.</LI>
<LI><P>Each job can have any number of optional Gerber files describing copper
layers, silkscreen, solder masks, etc.</LI>
<LI><P>All files must have the same offset and must be shown looking from the
top of the board, i.e., not mirrored.</LI>
<LI><P>Each job may have an optional tool list file indicating the tool names
used in the Excellon file and the diameter of each tool. This file is not necessary
if tool sizes are embedded in the Excellon file. A typical tool list file looks like:
<PRE>
T01 0.025in
T02 0.032in
T03 0.045in
</PRE>
</UL>
<P><A NAME="Verifying"></A></P>
<H2>Verifying the Output</H2>
<P>Before sending your job to be manufactured, it is imperative that you verify
the correctness of the output. Remember that GerbMerge comes with NO WARRANTY.
Manufacturing circuit boards costs real money and a single mistake can render
an entire lot of boards unusable.
<P>I recommend the following programs for viewing the final output data. Take
the time to become very familiar with at least one of these tools and to use
it before every job you send out for manufacture.
<DL>
<DT><B>gerbv</B></DT>
<DD>For Linux, the best option (currently) for viewing Gerber and Excellon files
is the <A HREF="http://gerbv.sourceforge.net"><TT>gerbv</TT></A> program. Simply
type in the names of all files generated by GerbMerge as parameters to <TT>gerbv</TT>:
<CENTER><PRE>gerbv merged.*.ger merged.*.xln</PRE></CENTER></DD>
<DT><B>GC-Prevue</B></DT>
<DD><P>For Windows, <A HREF="http://www.graphicode.com">GC-Prevue</A> is a good program
that I have used often. It is a free program. GraphiCode makes lots of other, more
powerful Gerber manipulation and viewing programs but they are quite pricey ($495 and up).</DD>
<DT><B>ViewMate</B></DT>
<DD><P>Another free Windows program, <A HREF="http://www.pentalogix.com">ViewMate</A> is similar
to GC-Prevue. I have not used ViewMate much, but that is mostly due to familiarity with
GC-Prevue. The two programs are comparable, although I'm sure that someone who is much
more familiar with both could point out some differences.</DD>
</DL>
<P><A NAME="Limitations"></A></P>
<H2>Limitations</H2>
<UL>
<LI>This program has mainly been tested with output from the Eagle CAD program.
Limited testing has been performed with Orcad, Protel, and PCB.
Other CAD programs will NOT WORK with a very high probability, as the input
parser is quite primitive.
<P>If you have the need/motivation to adapt GerbMerge to other CAD programs,
have a look at the <TT>gerber2pdf</TT> program. It is written in Python and
implements a much more complete RS274-X input file parser. Combining GerbMerge
with <TT>gerber2pdf</TT> should be a fairly simple exercise. Also, feel free to
send us samples of Gerber/Excellon output of your CAD tool and we'll see if we can
add support for it.
<LI><P>This program handles apertures that are rectangles, ovals, circles, macros
without parameters or operators, and Eagle octagons (which are defined using a macro with a single parameter, hence currently handled as a special case).
<LI><P>The panelizing capabilities of this program do not allow for arbitrary
placement of jobs, although there is a fair amount of flexibility.
<LI><P>All jobs are assumed to be rectangular in shape. Non-rectangular jobs
can be handled but will lead to wasted space in the final panel.
<LI><P>A maximum of 26 different drill sizes is supported for generating a
fabrication drawing.</LI>
</UL>
<P><A NAME="ProgramOptions"></A></P>
<H2>Program Options</H2>
<DL>
<DT>--octagons=normal</DT>
<DT>--octagons=rotate</DT>
<DD>The <TT>--octagons</TT> option affects how the octagon aperture is defined in the output files. The parameter
to this option must either be <TT>rotate</TT> or <TT>normal</TT>. Normally,
octagons begin at an angle of 22.5 degrees, but some Gerber viewers have a problem
with that (notably CircuitMaker from LPKF). These programs expect octagons to begin
at 0.0 degrees.
<P>The <TT>--octagons=normal</TT> option is the default (22.5 degrees) and need not
be specified. A rotation of 0.0 degrees can be achieved by specifying <TT>--octagons=rotate</TT>.</DD>
<P><DT>--random-search</DT>
<DD>This option is the default when only a configuration file is specified (see the documentation on <A HREF="autosearch.html">Automatic Placement</A> for more information). It indicates that a randomized search of possible job tilings is
to be performed. This option does not make sense when a layout file is specified.</DD>
<P><DT>--full-search</DT>
<DD>This option may be specified to indicate that all possible job tilings are to be searched (see the documentation on <A HREF="autosearch.html">Automatic Placement</A> for more information). This option does not make sense when a layout file
is specified.</DD>
<P><DT>--rs-fsjobs=N</DT>
<DD>This option is used with randomized search to indicate how many jobs are to undergo full search for each tiling. See the documentation on <A HREF="autosearch.html">Automatic Placement</A> for more information.</DD>
<P><DT>--place-file=filename</DT>
<DD>This option performs a panel layout based upon absolute job positions in
the given text file, rather than by random/full search or by a layout file.
The placement file created by GerbMerge can be used as an input file to
this option in order to recreate a previous layout.</DD>
<P><DT>--no-trim-gerber</DT>
<DD>This option prevents GerbMerge from trying to trim all Gerber data to lie within the
extents of a given job's board outline. Normally, GerbMerge will try to do so to prevent
one job's Gerber data (most notably, silkscreen lines for connectors that protrude from
the board) from interfering with a neighboring job on the final panel. Specify this
command-line option if you do not want this trimming to occur.</DD>
<P><DT>--no-trim-excellon</DT>
<DD>This option prevents GerbMerge from trying to trim all Excellon data to lie within the
extents of a given job's board outline. Normally, GerbMerge will try to do so to prevent
one job's drill holes from landing in the middle of a neighboring job on the final panel. Specify
this command-line option if you do not want this trimming to occur.</DD>
<P><DT>--search-timeout=seconds</DT>
<DD>When random placements are used, this option can be used to automatically terminate the
search process after the specified number of seconds. If the number of seconds is 0 or this
option is not specified, then random placements are tried forever, until Ctrl-C is pressed
to stop the process and keep the best placement so far.</DD>
<P><DT>-h, --help</DT>
<DD>The '<TT>-h</TT>' or '<TT>--help</TT>' option prints a brief summary of available options.
<P><DT>-v, --version</DT>
<DD>The '<TT>-v</TT>' or '<TT>--version</TT>' option prints the current program version and author contact information.</DD>
</DL>
<P><A NAME="Copyright"></A></P>
<H2>Copyright &amp; License</H2>
<P>Copyright &COPY; 2011 <A HREF="http://ruggedcircuits.com">Rugged Circuits LLC</A>. All Rights Reserved.
mailto: <A HREF="mailto:support@ruggedcircuits.com?subject=GerbMerge">support@ruggedcircuits.com</A>
<P>GerbMerge comes with ABSOLUTELY NO WARRANTY. This
is free software licensed under the terms of the <A HREF="gpl.html">GNU General
Public License</A> Version 3. You are welcome to redistribute this software
under certain conditions. For more details, see the previous link or
visit <A HREF="http://www.fsf.org">The Free Software Foundation</A>.
<P><A NAME="Todo"></A></P>
<H2>To Do</H2>
<OL>
<LI>Accept outputs from more CAD programs</LI>
<LI>A graphical interface for interactive placement</LI>
<LI>Better reporting of parse errors in the layout and configuration files</LI>
<LI>Implement simple primitive for panelizing a single job in an array</LI>
<LI>More intelligent placement algorithms, possibly based on the fabric cutting problem.</LI>
<LI>Accept aperture macro parameters and operators
</OL>
<P><A NAME="Credits"></A></P>
<H2>Credits</H2>
<P>Thanks to Jace Browning for major contributions to this code. This help file is based on a template for the help file for mxTools
by <A HREF="http://starship.python.net/crew/lemburg">M.A. Lemburg</A>.
This software was created with <A HREF="http://www.vim.org/">VIM</A>;
thanks to the authors of this program and special thanks for
the Python syntax support. Thanks to M.A. Lemburg for his
<A HREF="http://www.egenix.com/files/python/eGenix-mx-Extensions.html">mxBase</A>
package, Mike Fletcher for his
<A HREF="http://simpleparse.sourceforge.net">SimpleParse</A> package, and
the authors of <A HREF="http://gerbv.sourceforge.net">gerbv</A>, a great
Gerber file viewer for Linux/Mac OS X, and, of course, to the
<A HREF="http://www.python.org">Python</A> developers and
support community.</P>
<P>Thanks to Joe Pighetti for making me start writing this program, and to
the Grand Valley State University Firefighting Robot Team for making me finish it.</P>
<P>Thanks to Matt Kavalauskas for identifying Eagle's annulus and thermal macros and supporting
the development of the aperture macro code.</P>
<P>Thanks to Bohdan Zograf for the <A HREF="http://webhostingrating.com/libs/gerbmerge-be">Belorussian translation</A> of this documentation.</P>
<HR ALIGN=LEFT>
<P><CENTER><FONT SIZE="-1">&COPY; 2003-2011, Copyright by <A HREF="http://ruggedcircuits.com">Rugged Circuits LLC</A>; All Rights Reserved. mailto: <A HREF="mailto:support@ruggedcircuits.com?subject=GerbMerge">support@ruggedcircuits.com</A></FONT></CENTER>
</BODY>
</HTML>

272
doc/layout1.cfg Normal file
View File

@ -0,0 +1,272 @@
# This configuration file demonstrates panelizing a single job.
##############################################################################
# In the [DEFAULT] section you can create global names to save typing the same
# directory name, for example, over and over.
##############################################################################
[DEFAULT]
# Change projdir to wherever your project files are, for example:
#
# projdir = /home/stuff/projects/test
#
# or relative pathname from where you are running GerbMerge
#
# projdir = testdata
#
# or if all files are in the current directory (as in this example):
#
# projdir = .
projdir = .
# For convenience, this is the base name of the merged output files.
MergeOut = merge1
#############################################################################
# The [Options] section defines settings that control how the input files are
# read and how the output files are generated.
#############################################################################
[Options]
################################################################
#
# Settings that are very important
#
################################################################
# Option indicating name of file that maps Excellon tool codes to drill sizes.
# This is not necessary if the Excellon files have embedded tool sizes, or if a
# tool list is specified as part of the job description. The ToolList option
# here is the "last resort" for mapping tool codes to tool sizes. Most recent
# PCB programs embed drill size information right in the Excellon file, so this
# option should not be necessary and can be commented out.
#ToolList=proj1.drl
# Optional indication of the number of decimal places in input Excellon drill
# files. The default is 4 which works for recent versions of Eagle (since
# version 4.11r12), as well as Orcad and PCB. Older versions of Eagle use 3
# decimal places.
#ExcellonDecimals = 4
################################################################
#
# Settings that are somewhat important
#
################################################################
# Which layers to draw cut lines on. Omit this option or set to 'None' for no
# cut lines. Cut lines are borders around each job that serve as guides for
# cutting the panel into individual jobs. Option 'CutLineWidth' sets the
# thickness of these cut lines.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CutLineLayers = *topsilkscreen,*bottomsilkscreen
# Which layers to draw crop marks on. Omit this option or set to 'None' for no
# crop marks. Crop marks are small L-shaped marks at the 4 corners of the final
# panel. These practically define the extents of the panel and are required by
# some board manufacturers. Crop marks are also required if you want to leave
# extra space around the final panel for tooling or handling. Option
# 'CropMarkWidth' sets the thickness of these crop marks.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CropMarkLayers = *topsilkscreen,*bottomsilkscreen
# Set this option to the name of a file in which to write a Gerber fabrication
# drawing. Some board manufacturers require a fabrication drawing with panel
# dimensions and drill hit marks and drill legend. There's no harm in creating
# this file...you can ignore it if you don't need it.
FabricationDrawingFile = %(mergeout)s.fab
# If FabricationDrawingFile is specified, you can provide an optional file name
# of a file containing arbitrary text to add to the fabrication drawing. This
# text can indicate manufacturing information, contact information, etc.
#FabricationDrawingText = %(projdir)s/fabdwg.txt
# Option to generate leading zeros in the output Excellon drill file, i.e., to
# NOT use leading-zero suppression. Some Gerber viewers cannot properly guess
# the Excellon file format when there are no leading zeros. Set this option to
# 1 if your Gerber viewer is putting the drill holes in far off places that do
# not line up with component pads.
ExcellonLeadingZeros = 0
# Optional additional Gerber layer on which to draw a rectangle defining the
# extents of the entire panelized job. This will create a Gerber file (with
# name specified by this option) that simply contains a rectangle defining the
# outline of the final panel. This outline file is useful for circuit board
# milling to indicate a path for the router tool. There's no harm in creating
# this file...you can ignore it if you don't need it.
OutlineLayerFile = %(mergeout)s.oln
# Optional additional Gerber layer on which to draw horizontal and vertical
# lines describing where to score (i.e., V-groove) the panel so that jobs
# can easily snap apart. These scoring lines will be drawn half-way between
# job borders.
ScoringFile = %(mergeout)s.sco
# Set the maximum dimensions of the final panel, if known. You can set the
# dimensions of the maximum panel size supported by your board manufacturer,
# and GerbMerge will print an error message if your layout exceeds these
# dimensions. Alternatively, when using automatic placement, the panel sizes
# listed here constrain the random placements such that only placements that
# fit within the given panel dimensions will be considered. The dimensions are
# specified in inches.
PanelWidth = 12.6
PanelHeight = 7.8
# Set the amount of extra space to leave around the edges of the panel to
# simplify tooling and handling. These margins are specified in inches, and
# default to 0" if not specified. These spacings will only be visible to the
# board manufacturer if you enable crop marks (see CropMarkLayers above) or use.
LeftMargin = 0.1
RightMargin = 0.1
TopMargin = 0.1
BottomMargin = 0.1
################################################################
#
# Settings that are probably not important
#
################################################################
# Set the inter-job spacing (inches) in both the X-dimension (width) and
# Y-dimension (height). Normally these would be the same unless you're trying
# really hard to make your jobs fit into a panel of exact size and you need to
# tweak these spacings to make it work. 0.125" is probably generous, about half
# that is practical for using a band saw, but you probably want to leave it at
# 0.125" if you have copper features close to the board edges and/or are using
# less precise tools, like a hacksaw, for separating the boards.
XSpacing = 0.125
YSpacing = 0.125
# Width of cut lines, in inches. The default value is 0.01". These are drawn on
# the layers specified by CutLineLayers.
CutLineWidth = 0.01
# Width of crop marks, in inches. The default value is 0.01". These are drawn on
# the layers specified by CropMarkLayers.
CropMarkWidth = 0.01
# This option is intended to reduce the probability of forgetting to include a
# layer in a job description when panelizing two or more different jobs.
# Unless this option is set to 1, an error will be raised if some jobs do not
# have the same layer names as the others, i.e., are missing layers. For
# example, if one job has a top-side soldermask layer and another doesn't, that
# could be a mistake. Setting this option to 1 prevents this situation from
# raising an error.
AllowMissingLayers = 0
# This option is intended to reduce the number of drills in the output by
# eliminating drill sizes that are too close to make a difference. For example,
# it probably does not make sense to have two separate 0.031" and 0.0315"
# drills. The DrillClusterTolerance value specifies how much tolerance is
# allowed in drill sizes, in units of inches. Multiple drill tools that span
# twice this tolerance will be clustered into a single drill tool. For example,
# a set of 0.031", 0.0315", 0.032", and 0.034" drills will all be replaced by a
# single drill tool of diameter (0.031"+0.034")/2 = 0.0325". It is guaranteed
# that all original drill sizes will be no farther than DrillClusterTolerance
# from the drill tool size generated by clustering.
#
# Setting DrillClusterTolerance to 0 disables clustering.
DrillClusterTolerance = 0.002
# Use this option to automatically thicken features on particular layers. This
# is intended for thickening silkscreen to some minimum width. The value of
# this option must be a comma-separated list of layer names followed by minimum
# feature sizes (in inches) for that layer. Comment this out to disable thickening.
MinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008
##############################################################################
# This section sets the name of merged output files. Each assignment below
# specifies a layer name and the file name that is to be written for that
# merged layer. Except for the BoardOutline and Drills layer names, all other
# layer names must begin with an asterisk '*'. The special layer name Placement
# is used to specify the placement file that can be used with the
# '--place-file' command-line option in a future invocation of GerbMerge. The
# special layer name ToolList is used to specify the file name that represents
# the tool list for the panelized job.
#
# By default, if this section is omitted or no layername=filename assignment is
# made, the following files are generated:
#
# BoardOutline = merged.boardoutline.ger
# Drills = merged.drills.xln
# Placement = merged.placement.txt
# ToolList = merged.toollist.drl
# *layername = merged.layername.ger
# (for example: 'merged.toplayer.ger', 'merged.silkscreen.ger')
#
# Any assignment that does not begin with '*' or is not one of the reserved
# names BoardOutline, Drills, ToolList, or Placement is a generic string
# assignment that can be used for string substitutions, to save typing.
##############################################################################
[MergeOutputFiles]
Prefix = %(mergeout)s
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
ToolList = toollist.%(prefix)s.drl
Placement = placement.%(prefix)s.txt
##############################################################################
# The remainder of the file specifies the jobs to be panelized. Each job is
# specified in its own section. To each job you can assign a job name, which
# will be the name of the section in square brackets (e.g., [Proj1]). This job
# name is used in the layout file (if used) to refer to the job.
#
# Job names are case-sensitive, but do not create job names that are the same
# except for the case of the characters, as this may cause problems during
# layout. Job names may only contain the following characters:
#
# a-z A-Z 0-9 _
#
# In addition, job names must begin with a letter (a-z or A-Z).
##############################################################################
[Proj1]
# You can set any options you like to make generating filenames easier, like
# Prefix. This is just a helper option, not a reserved name. Note, however,
# that you must write %(prefix)s below, in ALL LOWERCASE.
#
# Note how we are making use of the 'projdir' string defined way up at the top
# in the [DEFAULT] section to save some typing. By setting 'projdir=somedir'
# the expression '%(projdir)s/proj1' expands to 'somedir/proj1'.
Prefix=%(projdir)s/proj1
# List all the layers that participate in this job. Required layers are Drills
# and BoardOutline and have no '*' at the beginning. Optional layers have
# names chosen by you and begin with '*'. You should choose consistent layer
# names across all jobs.
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
# If this job does not have drill tool sizes embedded in the Excellon file, it
# needs to have a separate tool list file that maps tool names (e.g., 'T01') to
# tool diameter. This may be the global tool list specified in the [Options]
# section with the ToolList parameter. If this job doesn't have embedded tool
# sizes, and uses a different tool list than the global one, you can specify it
# here.
#ToolList=proj1.drl
# If this job has a different ExcellonDecimals setting than the global setting
# in the [Options] section above, it can be overridden here.
#ExcellonDecimals = 3
# You can set a 'Repeat' parameter for this job when using automatic placement
# (i.e., no *.def file) to indicate how many times this job should appear in
# the final panel. When using manual placement, this option is ignored.
#Repeat = 5

19
doc/layout1.def Normal file
View File

@ -0,0 +1,19 @@
# This example simply takes the small Proj1 board and panelizes
# it in a 2x4 array. To demonstrate rotation, the second column
# consists of rotated jobs. You wouldn't really do it this way,
# of course, as it wastes space.
Row {
Col {
Proj1
Proj1
Proj1
Proj1
}
Col {
Proj1 Rotate
Proj1 Rotate
Proj1 Rotate
Proj1 Rotate
}
}

282
doc/layout2.cfg Normal file
View File

@ -0,0 +1,282 @@
# This configuration file demonstrates panelizing multiple, different jobs.
# We panelize the HEXAPOD job and several copies of the Proj1 job.
##############################################################################
# In the [DEFAULT] section you can create global names to save typing the same
# directory name, for example, over and over.
##############################################################################
[DEFAULT]
# Change projdir to wherever your project files are, for example:
#
# projdir = /home/stuff/projects/test
#
# or relative pathname from where you are running GerbMerge
#
# projdir = testdata
#
# or if all files are in the current directory (as in this example):
#
# projdir = .
projdir = .
# For convenience, this is the base name of the merged output files.
MergeOut = merge2
#############################################################################
# The [Options] section defines settings that control how the input files are
# read and how the output files are generated.
#############################################################################
[Options]
################################################################
#
# Settings that are very important
#
################################################################
# Option indicating name of file that maps Excellon tool codes to drill sizes.
# This is not necessary if the Excellon files have embedded tool sizes, or if a
# tool list is specified as part of the job description. The ToolList option
# here is the "last resort" for mapping tool codes to tool sizes. Most recent
# PCB programs embed drill size information right in the Excellon file, so this
# option should not be necessary and can be commented out.
#ToolList=proj1.drl
# Optional indication of the number of decimal places in input Excellon drill
# files. The default is 4 which works for recent versions of Eagle (since
# version 4.11r12), as well as Orcad and PCB. Older versions of Eagle use 3
# decimal places.
#ExcellonDecimals = 4
################################################################
#
# Settings that are somewhat important
#
################################################################
# Which layers to draw cut lines on. Omit this option or set to 'None' for no
# cut lines. Cut lines are borders around each job that serve as guides for
# cutting the panel into individual jobs. Option 'CutLineWidth' sets the
# thickness of these cut lines.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CutLineLayers = *topsilkscreen,*bottomsilkscreen
# Which layers to draw crop marks on. Omit this option or set to 'None' for no
# crop marks. Crop marks are small L-shaped marks at the 4 corners of the final
# panel. These practically define the extents of the panel and are required by
# some board manufacturers. Crop marks are also required if you want to leave
# extra space around the final panel for tooling or handling. Option
# 'CropMarkWidth' sets the thickness of these crop marks.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CropMarkLayers = *topsilkscreen,*bottomsilkscreen
# Set this option to the name of a file in which to write a Gerber fabrication
# drawing. Some board manufacturers require a fabrication drawing with panel
# dimensions and drill hit marks and drill legend. There's no harm in creating
# this file...you can ignore it if you don't need it.
FabricationDrawingFile = %(mergeout)s.fab
# If FabricationDrawingFile is specified, you can provide an optional file name
# of a file containing arbitrary text to add to the fabrication drawing. This
# text can indicate manufacturing information, contact information, etc.
FabricationDrawingText = %(projdir)s/fabdwg.txt
# Option to generate leading zeros in the output Excellon drill file, i.e., to
# NOT use leading-zero suppression. Some Gerber viewers cannot properly guess
# the Excellon file format when there are no leading zeros. Set this option to
# 1 if your Gerber viewer is putting the drill holes in far off places that do
# not line up with component pads.
ExcellonLeadingZeros = 0
# Optional additional Gerber layer on which to draw a rectangle defining the
# extents of the entire panelized job. This will create a Gerber file (with
# name specified by this option) that simply contains a rectangle defining the
# outline of the final panel. This outline file is useful for circuit board
# milling to indicate a path for the router tool. There's no harm in creating
# this file...you can ignore it if you don't need it.
OutlineLayerFile = %(mergeout)s.oln
# Optional additional Gerber layer on which to draw horizontal and vertical
# lines describing where to score (i.e., V-groove) the panel so that jobs
# can easily snap apart. These scoring lines will be drawn half-way between
# job borders.
ScoringFile = %(mergeout)s.sco
# Set the maximum dimensions of the final panel, if known. You can set the
# dimensions of the maximum panel size supported by your board manufacturer,
# and GerbMerge will print an error message if your layout exceeds these
# dimensions. Alternatively, when using automatic placement, the panel sizes
# listed here constrain the random placements such that only placements that
# fit within the given panel dimensions will be considered. The dimensions are
# specified in inches.
PanelWidth = 12.6
PanelHeight = 7.8
# Set the amount of extra space to leave around the edges of the panel to
# simplify tooling and handling. These margins are specified in inches, and
# default to 0" if not specified. These spacings will only be visible to the
# board manufacturer if you enable crop marks (see CropMarkLayers above) or use
# an OutlineLayer.
LeftMargin = 0.1
RightMargin = 0.1
TopMargin = 0.1
BottomMargin = 0.1
################################################################
#
# Settings that are probably not important
#
################################################################
# Set the inter-job spacing (inches) in both the X-dimension (width) and
# Y-dimension (height). Normally these would be the same unless you're trying
# really hard to make your jobs fit into a panel of exact size and you need to
# tweak these spacings to make it work. 0.125" is probably generous, about half
# that is practical for using a band saw, but you probably want to leave it at
# 0.125" if you have copper features close to the board edges and/or are using
# less precise tools, like a hacksaw, for separating the boards.
XSpacing = 0.125
YSpacing = 0.125
# Width of cut lines, in inches. The default value is 0.01". These are drawn on
# the layers specified by CutLineLayers.
CutLineWidth = 0.01
# Width of crop marks, in inches. The default value is 0.01". These are drawn on
# the layers specified by CropMarkLayers.
CropMarkWidth = 0.01
# This option is intended to reduce the probability of forgetting to include a
# layer in a job description when panelizing two or more different jobs.
# Unless this option is set to 1, an error will be raised if some jobs do not
# have the same layer names as the others, i.e., are missing layers. For
# example, if one job has a top-side soldermask layer and another doesn't, that
# could be a mistake. Setting this option to 1 prevents this situation from
# raising an error.
AllowMissingLayers = 1
# This option is intended to reduce the number of drills in the output by
# eliminating drill sizes that are too close to make a difference. For example,
# it probably does not make sense to have two separate 0.031" and 0.0315"
# drills. The DrillClusterTolerance value specifies how much tolerance is
# allowed in drill sizes, in units of inches. Multiple drill tools that span
# twice this tolerance will be clustered into a single drill tool. For example,
# a set of 0.031", 0.0315", 0.032", and 0.034" drills will all be replaced by a
# single drill tool of diameter (0.031"+0.034")/2 = 0.0325". It is guaranteed
# that all original drill sizes will be no farther than DrillClusterTolerance
# from the drill tool size generated by clustering.
#
# Setting DrillClusterTolerance to 0 disables clustering.
DrillClusterTolerance = 0.002
# Use this option to automatically thicken features on particular layers. This
# is intended for thickening silkscreen to some minimum width. The value of
# this option must be a comma-separated list of layer names followed by minimum
# feature sizes (in inches) for that layer. Comment this out to disable thickening.
MinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008
##############################################################################
# This section sets the name of merged output files. Each assignment below
# specifies a layer name and the file name that is to be written for that
# merged layer. Except for the BoardOutline and Drills layer names, all other
# layer names must begin with an asterisk '*'. The special layer name Placement
# is used to specify the placement file that can be used with the
# '--place-file' command-line option in a future invocation of GerbMerge. The
# special layer name ToolList is used to specify the file name that represents
# the tool list for the panelized job.
#
# By default, if this section is omitted or no layername=filename assignment is
# made, the following files are generated:
#
# BoardOutline = merged.boardoutline.ger
# Drills = merged.drills.xln
# Placement = merged.placement.txt
# ToolList = merged.toollist.drl
# *layername = merged.layername.ger
# (for example: 'merged.toplayer.ger', 'merged.silkscreen.ger')
#
# Any assignment that does not begin with '*' or is not one of the reserved
# names BoardOutline, Drills, ToolList, or Placement is a generic string
# assignment that can be used for string substitutions, to save typing.
##############################################################################
[MergeOutputFiles]
Prefix = %(mergeout)s
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
ToolList = toollist.%(prefix)s.drl
Placement = placement.%(prefix)s.txt
##############################################################################
# The remainder of the file specifies the jobs to be panelized. Each job is
# specified in its own section. To each job you can assign a job name, which
# will be the name of the section in square brackets (e.g., [Proj1]). This job
# name is used in the layout file (if used) to refer to the job.
#
# Job names are case-sensitive, but do not create job names that are the same
# except for the case of the characters, as this may cause problems during
# layout. Job names may only contain the following characters:
#
# a-z A-Z 0-9 _
#
# In addition, job names must begin with a letter (a-z or A-Z).
##############################################################################
[Proj1]
# You can set any options you like to make generating filenames easier, like
# Prefix. This is just a helper option, not a reserved name. Note, however,
# that you must write %(prefix)s below, in ALL LOWERCASE.
#
# Note how we are making use of the 'projdir' string defined way up at the top
# in the [DEFAULT] section to save some typing. By setting 'projdir=somedir'
# the expression '%(projdir)s/proj1' expands to 'somedir/proj1'.
Prefix=%(projdir)s/proj1
# List all the layers that participate in this job. Required layers are Drills
# and BoardOutline and have no '*' at the beginning. Optional layers have
# names chosen by you and begin with '*'. You should choose consistent layer
# names across all jobs.
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
# If this job does not have drill tool sizes embedded in the Excellon file, it
# needs to have a separate tool list file that maps tool names (e.g., 'T01') to
# tool diameter. This may be the global tool list specified in the [Options]
# section with the ToolList parameter. If this job doesn't have embedded tool
# sizes, and uses a different tool list than the global one, you can specify it
# here.
#ToolList=proj1.drl
# If this job has a different ExcellonDecimals setting than the global setting
# in the [Options] section above, it can be overridden here.
#ExcellonDecimals = 3
# You can set a 'Repeat' parameter for this job when using automatic placement
# (i.e., no *.def file) to indicate how many times this job should appear in
# the final panel. When using manual placement, this option is ignored.
Repeat = 11
[Hexapod]
Prefix=%(projdir)s/hexapod
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor

57
doc/layout2.def Normal file
View File

@ -0,0 +1,57 @@
# This layout merges a Hexapod and Proj1 boards into a single
# panel. The layout demonstrates nested rows and columns. The
# final arrangement looks like this (make sure you are looking
# at this document with a fixed-width font like Courier):
#
# +-----------------------------------------------------+
# | Proj1 | Proj1 | Proj1 | Proj1 | Proj1 | |
# | | | | | | |
# | | | | | | |
# +---------+---------+---------+---------+--------+ |
# | |
# | +-------+-------+
# | | P | P |
# +--------------------------------+ | r | r |
# | | | o | o |
# | | | j | j |
# | | | 1 | 1 |
# | | +-------+-------+
# | | | P | P |
# | | | r | r |
# | | | o | o |
# | Hexapod | | j | j |
# | | | 1 | 1 |
# | | +-------+-------+
# | | | P | P |
# | | | r | r |
# | | | o | o |
# | | | j | j |
# | | | 1 | 1 |
# +--------------------------------+----+-------+-------+
Row { // First row has the hexapod and 2x3 panel of
// rotated Proj1 jobs.
Hexapod
Col { // Could also write this as two separate 1x3 columns
Row { // First 1x2 row
Proj1 Rotate
Proj1 Rotate
}
Row { // Second 1x2 row above first one
Proj1 Rotate
Proj1 Rotate
}
Row { // Third 1x2 row above second row
Proj1 Rotate
Proj1 Rotate
}
} // end of column
} // end of first row
Row { // Second row has 5x1 panel of Proj1
Proj1
Proj1
Proj1
Proj1
Proj1
}

216
doc/layoutfile.html Normal file
View File

@ -0,0 +1,216 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>GerbMerge -- A Gerber-file merging program -- The Layout File</TITLE>
</HEAD>
<BODY BGCOLOR="#ffffff" LINK="#0000c0" VLINK="#8f008f">
<!-- -->
<P><FONT SIZE="+2">GerbMerge -- The Layout File</FONT></P>
<BLOCKQUOTE>
<P><A HREF="http://ruggedcircuits.com/gerbmerge"><FONT
SIZE="-1">Rugged Circuits LLC</FONT></A><FONT SIZE="-1"></FONT></P>
</BLOCKQUOTE>
<P><HR ALIGN=LEFT><TABLE WIDTH="100%" BORDER="0" CELLSPACING="2"
CELLPADDING="0">
<TR>
<TD><A HREF="index.html">Top-Level</A> | <A HREF="cfgfile.html">The Configuration File</A> | The Layout File | <A HREF="autosearch.html">Automatic Placement</A></TD>
</TR>
</TABLE><HR ALIGN=LEFT></P>
<H2>Introduction</H2>
<P>The layout file tells GerbMerge how to replicate and/or arrange the jobs that
you specified in the <A HREF="cfgfile.html">configuration file</A>. The layout
file must be specified when using manual relative placement. See the <A HREF="autosearch.html">Automatic
Placement</A> page for an alternative to using the layout file approach.
<P>The layout file is a plain text file that can be created with any
text editor.
<P>Have a look at the sample layout files <A HREF="layout1.def"><TT>layout1.def</TT></A>
and <A HREF="layout2.def"><TT>layout2.def</TT></A> for a quick overview of this file.
<P><A NAME="RowsCols"></A>
<H2>Rows and Columns</H2>
<P>The panel layout is specified in terms of cells. Each cell is part of either a
row or column of cells. Each row or column can itself be a part of a column or row,
respectively. In this way, a large variety of layouts can be specified. Unfortunately,
this scheme is fairly easy to implement in code, but it does not allow for arbitrary
placement of jobs.
<P>At the top level, you specify the layout of the final panel by specifying the
contents of each row, from left to right. Let's begin with an example. The input job, named <TT>example</TT> is as follows:
<CENTER><IMG SRC="ex1.png"></CENTER>
We will place three copies of this job, all in a row, using the following layout:
<PRE>
Row {
example # Jobs are listed in a row from left to right
example
example
}
</PRE>
The above layout file leads to the following panel:
<CENTER><IMG SRC="ex1a.png"></CENTER>
<P>The <TT>Row { .... }</TT> construct indicates a single row of the layout. While you
can add spaces and comments as you please, the word <TT>Row</TT> and its associated
open-bracket must appear on one line, each job name on a separate line, and the closing
bracket on its own line. Thus, the following is illegal:
<PRE>
Row { example example example } # Illegal!
</PRE>
<P>The word <TT>Rotate</TT> following a job indicates that the given instance of the job
is to be rotated by 90 degrees at its current position. For example:
<PRE>
Row {
example
example Rotate
example
}
</PRE>
The above layout file leads to the following panel:
<CENTER><IMG SRC="ex1b.png"></CENTER>
<P>Rows stack vertically beginning at the bottom of the panel and moving up. For example:
<PRE>
Row {
example
example
example
}
Row {
example
example
example
}
</PRE>
The above layout file leads to the following panel:
<CENTER><IMG SRC="ex1c.png"></CENTER>
<P>Suppose now that we want the two jobs on the right to be rotated so the final panel has
a smaller width, but larger height. We can try the following:
<PRE>
Row {
example
example
example Rotate
}
Row {
example
example
example Rotate
}
</PRE>
The above layout file leads to the following panel:
<CENTER><IMG SRC="ex1d.png"></CENTER>
<P>This layout is quite wasteful and not quite what we intended. The problem is that
GerbMerge stacks rows on top of each other based upon the highest job within a row. The
height of the first (bottom-most) row, then, is the height of the rotated job.
<P>What we really want is to
think of our layout in terms of columns (in this case). The first column should be two
jobs stacked on top of each other. The second column should be the same. While the third
column should be two jobs side by side. We can accomplish this effect by placing columns
within a single row. Within a column, jobs are listed in stacking order from bottom to top.
For example:
<PRE>
Row {
Col {
example
example
}
Col {
example
example
}
example Rotate
example Rotate
}
</PRE>
The above layout file leads to the following panel:
<CENTER><IMG SRC="ex1e.png"></CENTER>
<P>Study that layout file carefully. The panel has only a single row with 4 elements.
The first element is a column with two jobs. The second element (immediately to the
right of the first element) is also a column with two jobs. The third element is
a rotated job. The fourth and right-most element is a rotated job.
<P>In summary, a row is a list of cells that are laid out from left to right. A cell may
be a simple job, or it may be a column of jobs. The column is treated as a single row cell.
<P>Now, let's get fancy and embed a row within a column, like this:
<PRE>
Row {
Col {
example
Row { # This row sits above a job, in a column.
example Rotate # These are laid out left to right in the
example Rotate # middle of a column.
}
}
example Rotate # These continue left to right in the main row
example Rotate
}
</PRE>
The above layout file leads to the following panel:
<CENTER><IMG SRC="ex1f.png"></CENTER>
<P>In words, the job consists of a single row. The first cell in the row is a column.
The first cell in the column is a job. Above this cell is another row, which has two
cells (rotated jobs) laid out left-to-right.
<P>Keep re-reading the above until it makes sense!
<P>Here's a quiz: how can you modify the layout file so that a non-rotated job is
added in the blank space at the top-right of the above panel? Think before proceeding.
<P>To add a job on top of the two rotated jobs at the right of the panel, we must
convert those two jobs into a column, like this:
<PRE>
Row {
Col {
example
Row { # This row sits above a job, in a column.
example Rotate # These are laid out left to right in the
example Rotate # middle of a column.
}
}
Col {
Row {
example Rotate
example Rotate
}
example # This job sits on top of the two rotated jobs
}
}
</PRE>
The above layout file leads to the following panel:
<CENTER><IMG SRC="ex1g.png"></CENTER>
<P>Once you get the hang of thinking in terms of recursive rows and columns, the
process is not all that difficult. There is one important rule to remember, however:
<CENTER><B>Columns can only be defined within a row, and rows can only be defined within
a column.</B></CENTER>
<P>Make sure you have a look at the sample layout files <A HREF="layout1.def"><TT>layout1.def</TT></A>
and <A HREF="layout2.def"><TT>layout2.def</TT></A> for more examples.
<H2>Rotation Angles</H2>
<P>For rotating jobs, most users will simply want to rotate jobs by 90 degrees (counterclockwise). This is achieved using the <TT>Rotate</TT> keyword as described above. It is also possible to rotate jobs by 180 and 270 degrees for special applications, for example, when panelized jobs are not completely separate but need to interact with each other.
<P>The full list of rotation keywords recognized in the layout file is as follows:
<UL>
<LI><TT>Rotate</TT>, <TT>Rotate90</TT>: rotate by 90 degrees counterclockwise</LI>
<LI><TT>Rotate180</TT>: rotate by 180 degrees</LI>
<LI><TT>Rotate270</TT>: rotate by 270 degrees counterclockwise</LI>
</UL>
<HR ALIGN=LEFT>
<P><CENTER><FONT SIZE="-1">&COPY; 2003-2011, Copyright by <A HREF="http://ruggedcircuits.com">Rugged Circuits LLC</A>; All Rights Reserved. mailto: <A HREF="mailto:support@ruggedcircuits.com?subject=GerbMerge">support@ruggedcircuits.com</A></FONT></CENTER>
</BODY>
</HTML>

BIN
doc/sample.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

BIN
doc/sample2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

1
gerbmerge/__init__.py Normal file
View File

@ -0,0 +1 @@
# Placeholder for GerbMerge package

348
gerbmerge/amacro.py Normal file
View File

@ -0,0 +1,348 @@
#!/usr/bin/env python
"""
Define and manage aperture macros (%AM command). Currently,
only macros without replaceable parameters (e.g., $1, $2, etc.)
are supported.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import re
import string
import copy
import config
_macro_pat = re.compile(r'^%AM([^*]+)\*$')
# This list stores the expected types of parameters for each primitive type
# (e.g., outline, line, circle, polygon, etc.). None is used for undefined
# primitives. Each entry corresponds to the defined primitive code, and
# comprises a tuple of conversion functions (i.e., built-in int() and float()
# functions) that apply to all parameters AFTER the primitive code. For example,
# code 1 (circle) may be instantiated as:
# 1,1,0.025,0.0,0.0
# (the parameters are code, exposure type, diameter, X center, Y center).
# After the integer code, we expect an int for exposure type, then floats
# for the remaining three parameters. Thus, the entry for code 1 is
# (int, float, float, float).
PrimitiveParmTypes = (
None, # Code 0 -- undefined
(int, float, float, float), # Code 1 -- circle
(int, float, float, float, float, float, float), # Code 2 -- line (vector)
None, # Code 3 -- end-of-file for .DES files
(int, int, float, float, float, float, float), # Code 4 -- outline...takes any number of additional floats
(int, int, float, float, float, float), # Code 5 -- regular polygon
(float, float, float, float, float, int, float, float, float), # Code 6 -- moire
(float, float, float, float, float, float), # Code 7 -- thermal
None, # Code 8 -- undefined
None, # Code 9 -- undefined
None, # Code 10 -- undefined
None, # Code 11 -- undefined
None, # Code 12 -- undefined
None, # Code 13 -- undefined
None, # Code 14 -- undefined
None, # Code 15 -- undefined
None, # Code 16 -- undefined
None, # Code 17 -- undefined
None, # Code 18 -- undefined
None, # Code 19 -- undefined
(int, float, float, float, float, float), # Code 20 -- line (vector)...alias for code 2
(int, float, float, float, float, float), # Code 21 -- line (center)
(int, float, float, float, float, float) # Code 22 -- line (lower-left)
)
def rotatexy(x,y):
# Rotate point (x,y) counterclockwise 90 degrees about the origin
return (-y,x)
def rotatexypair(L, ix):
# Rotate list items L[ix],L[ix+1] by 90 degrees
L[ix],L[ix+1] = rotatexy(L[ix],L[ix+1])
def swapxypair(L, ix):
# Swap two list elements
L[ix],L[ix+1] = L[ix+1],L[ix]
def rotatetheta(th):
# Increase angle th in degrees by +90 degrees (counterclockwise).
# Handle modulo 360 issues
th += 90
if th >= 360:
th -= 360
return th
def rotatethelem(L, ix):
# Increase angle th by +90 degrees for a list element
L[ix] = rotatetheta(L[ix])
class ApertureMacroPrimitive:
def __init__(self, code=-1, fields=None):
self.code = code
self.parms = []
if fields is not None:
self.setFromFields(code, fields)
def setFromFields(self, code, fields):
# code is an integer describing the primitive type, and fields is
# a list of STRINGS for each parameter
self.code = code
# valids will be one of the PrimitiveParmTypes tuples above. Some are
# None to indicate illegal codes. We also set valids to None to indicate
# the macro primitive code is outside the range of known types.
try:
valids = PrimitiveParmTypes[code]
except:
valids = None
if valids is None:
raise RuntimeError, 'Undefined aperture macro primitive code %d' % code
# We expect exactly the number of fields required, except for macro
# type 4 which is an outline and has a variable number of points.
# For outlines, the second parameter indicates the number of points,
# each of which has an (X,Y) co-ordinate. Thus, we expect an Outline
# specification to have 1+1+2*N+1=3+2N fields:
# - first field is exposure
# - second field is number of points
# - 2*N fields for X,Y points
# - last field is rotation
if self.code==4:
if len(fields) < 2:
raise RuntimeError, 'Outline macro primitive has way too few fields'
try:
N = int(fields[1])
except:
raise RuntimeError, 'Outline macro primitive has non-integer number of points'
if len(fields) != (3+2*N):
raise RuntimeError, 'Outline macro primitive has %d fields...expecting %d fields' % (len(fields), 3+2*N)
else:
if len(fields) != len(valids):
raise RuntimeError, 'Macro primitive has %d fields...expecting %d fields' % (len(fields), len(valids))
# Convert each parameter on the input line to an entry in the self.parms
# list, using either int() or float() conversion.
for parmix in range(len(fields)):
try:
converter = valids[parmix]
except:
converter = float # To handle variable number of points in Outline type
try:
self.parms.append(converter(fields[parmix]))
except:
raise RuntimeError, 'Aperture macro primitive parameter %d has incorrect type' % (parmix+1)
def setFromLine(self, line):
# Account for DOS line endings and get rid of line ending and '*' at the end
line = line.replace('\x0D', '')
line = line.rstrip()
line = line.rstrip('*')
fields = line.split(',')
try:
try:
code = int(fields[0])
except:
raise RuntimeError, 'Illegal aperture macro primitive code "%s"' % fields[0]
self.setFromFields(code, fields[1:])
except:
print '='*20
print "==> ", line
print '='*20
raise
def rotate(self):
if self.code == 1: # Circle: nothing to do
pass
elif self.code in (2,20): # Line (vector): fields (2,3) and (4,5) must be rotated, no need to
# rotate field 6
rotatexypair(self.parms, 2)
rotatexypair(self.parms, 4)
elif self.code == 21: # Line (center): fields (3,4) must be rotated, and field 5 incremented by +90
rotatexypair(self.parms, 3)
rotatethelem(self.parms, 5)
elif self.code == 22: # Line (lower-left): fields (3,4) must be rotated, and field 5 incremented by +90
rotatexypair(self.parms, 3)
rotatethelem(self.parms, 5)
elif self.code == 4: # Outline: fields (2,3), (4,5), etc. must be rotated, the last field need not be incremented
ix = 2
for pts in range(self.parms[1]): # parms[1] is the number of points
rotatexypair(self.parms, ix)
ix += 2
#rotatethelem(self.parms, ix)
elif self.code == 5: # Polygon: fields (2,3) must be rotated, and field 5 incremented by +90
rotatexypair(self.parms, 2)
rotatethelem(self.parms, 5)
elif self.code == 6: # Moire: fields (0,1) must be rotated, and field 8 incremented by +90
rotatexypair(self.parms, 0)
rotatethelem(self.parms, 8)
elif self.code == 7: # Thermal: fields (0,1) must be rotated, and field 5 incremented by +90
rotatexypair(self.parms, 0)
rotatethelem(self.parms, 5)
def __str__(self):
# Construct a string with ints as ints and floats as floats
s = '%d' % self.code
for parmix in range(len(self.parms)):
valids = PrimitiveParmTypes[self.code]
format = ',%f'
try:
if valids[parmix] is int:
format = ',%d'
except:
pass # '%f' is OK for Outline extra points
s += format % self.parms[parmix]
return s
def writeDef(self, fid):
fid.write('%s*\n' % str(self))
class ApertureMacro:
def __init__(self, name):
self.name = name
self.prim = []
def add(self, prim):
self.prim.append(prim)
def rotate(self):
for prim in self.prim:
prim.rotate()
def rotated(self):
# Return copy of ourselves, rotated. Replace 'R' as the first letter of the
# macro name. We don't append because we like to be able to count the
# number of aperture macros by stripping off the leading character.
M = copy.deepcopy(self)
M.rotate()
M.name = 'R'+M.name[1:]
return M
def dump(self, fid=sys.stdout):
fid.write(str(self))
def __str__(self):
s = '%s:\n' % self.name
s += self.hash()
return s
def hash(self):
s = ''
for prim in self.prim:
s += ' '+str(prim)+'\n'
return s
def writeDef(self, fid):
fid.write('%%AM%s*\n' % self.name)
for prim in self.prim:
prim.writeDef(fid)
fid.write('%\n')
def parseApertureMacro(s, fid):
match = _macro_pat.match(s)
if match:
name = match.group(1)
M = ApertureMacro(name)
for line in fid:
if line[0]=='%':
return M
P = ApertureMacroPrimitive()
P.setFromLine(line)
M.add(P)
else:
raise RuntimeError, "Premature end-of-file while parsing aperture macro"
else:
return None
# This function adds the new aperture macro AM to the global aperture macro
# table. The return value is the modified macro (name modified to be its global
# name). macro.
def addToApertureMacroTable(AM):
GAMT = config.GAMT
# Must sort keys by integer value, not string since 99 comes before 100
# as an integer but not a string.
keys = map(int, map(lambda K: K[1:], GAMT.keys()))
keys.sort()
if len(keys):
lastCode = keys[-1]
else:
lastCode = 0
mcode = 'M%d' % (lastCode+1)
AM.name = mcode
GAMT[mcode] = AM
return AM
if __name__=="__main__":
# Create a funky aperture macro with all the fixins, and make sure
# it rotates properly.
M = ApertureMacro('TEST')
# X and Y axes
M.add(ApertureMacroPrimitive(2, ('1', '0.0025', '0.0', '-0.1', '0.0', '0.1', '0.0')))
M.add(ApertureMacroPrimitive(2, ('1', '0.0025', '0.0', '-0.1', '0.0', '0.1', '90.0')))
# A circle in the top-right quadrant, touching the axes
M.add(ApertureMacroPrimitive(1, ('1', '0.02', '0.01', '0.01')))
# A line of slope -1 centered on the above circle, of thickness 5mil, length 0.05
M.add(ApertureMacroPrimitive(2, ('1', '0.005', '0.0', '0.02', '0.02', '0.0', '0.0')))
# A narrow vertical rectangle centered on the circle of width 2.5mil
M.add(ApertureMacroPrimitive(21, ('1', '0.0025', '0.03', '0.01', '0.01', '0.0')))
# A 45-degree line in the third quadrant, not quite touching the origin
M.add(ApertureMacroPrimitive(22, ('1', '0.02', '0.01', '-0.03', '-0.03', '45')))
# A right triangle in the second quadrant
M.add(ApertureMacroPrimitive(4, ('1', '4', '-0.03', '0.01', '-0.03', '0.03', '-0.01', '0.01', '-0.03', '0.01', '0.0')))
# A pentagon in the fourth quadrant, rotated by 15 degrees
M.add(ApertureMacroPrimitive(5, ('1', '5', '0.03', '-0.03', '0.02', '15')))
# A moire in the first quadrant, beyond the circle, with 2 annuli
M.add(ApertureMacroPrimitive(6, ('0.07', '0.07', '0.04', '0.005', '0.01', '2', '0.005', '0.04', '0.0')))
# A thermal in the second quadrant, beyond the right triangle
M.add(ApertureMacroPrimitive(7, ('-0.07', '0.07', '0.03', '0.02', '0.005', '15')))
MR = M.rotated()
# Generate the Gerber so we can view it
fid = file('amacro.ger', 'wt')
print >> fid, \
"""G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%"""
M.writeDef(fid)
MR.writeDef(fid)
print >> fid, \
"""%ADD10TEST*%
%ADD11TESTR*%
D10*
X010000Y010000D03*
D11*
X015000Y010000D03*
M02*"""
fid.close()
print M
print MR

335
gerbmerge/aptable.py Normal file
View File

@ -0,0 +1,335 @@
#!/usr/bin/env python
"""
Manage apertures, read aperture table, etc.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import re
import string
import config
import amacro
import util
# Recognized apertures and re pattern that matches its definition Thermals and
# annuli are generated using macros (see the eagle.def file) but only on inner
# layers. Octagons are also generated as macros (%AMOC8) but we handle these
# specially as the Eagle macro uses a replaceable macro parameter ($1) and
# GerbMerge doesn't handle these yet...only fixed macros (no parameters) are
# currently supported.
Apertures = (
('Rectangle', re.compile(r'^%AD(D\d+)R,([^X]+)X([^*]+)\*%$'), '%%AD%sR,%.5fX%.5f*%%\n'),
('Circle', re.compile(r'^%AD(D\d+)C,([^*]+)\*%$'), '%%AD%sC,%.5f*%%\n'),
('Oval', re.compile(r'^%AD(D\d+)O,([^X]+)X([^*]+)\*%$'), '%%AD%sO,%.5fX%.5f*%%\n'),
('Octagon', re.compile(r'^%AD(D\d+)OC8,([^*]+)\*%$'), '%%AD%sOC8,%.5f*%%\n'), # Specific to Eagle
('Macro', re.compile(r'^%AD(D\d+)([^*]+)\*%$'), '%%AD%s%s*%%\n')
)
# This loop defines names in this module like 'Rectangle',
# which are element 0 of the Apertures list above. So code
# will be like:
# import aptable
# A = aptable.Aperture(aptable.Rectangle, ......)
for ap in Apertures:
globals()[ap[0]] = ap
class Aperture:
def __init__(self, aptype, code, dimx, dimy=None):
assert aptype in Apertures
self.apname, self.pat, self.format = aptype
self.code = code
self.dimx = dimx # Macro name for Macro apertures
self.dimy = dimy # None for Macro apertures
if self.apname in ('Circle', 'Octagon', 'Macro'):
assert (dimy is None)
def isRectangle(self):
return self.apname == 'Rectangle'
def rectangleAsRect(self, X, Y):
"""Return a 4-tuple (minx,miny,maxx,maxy) describing the area covered by
this Rectangle aperture when flashed at center co-ordinates (X,Y)"""
dx = util.in2gerb(self.dimx)
dy = util.in2gerb(self.dimy)
if dx & 1: # Odd-sized: X extents are (dx+1)/2 on the left and (dx-1)/2 on the right
xm = (dx+1)/2
xp = xm-1
else: # Even-sized: X extents are X-dx/2 and X+dx/2
xm = xp = dx/2
if dy & 1: # Odd-sized: Y extents are (dy+1)/2 below and (dy-1)/2 above
ym = (dy+1)/2
yp = ym-1
else: # Even-sized: Y extents are Y-dy/2 and Y+dy/2
ym = yp = dy/2
return (X-xm, Y-ym, X+xp, Y+yp)
def getAdjusted(self, minimum):
"""
Adjust aperture properties to conform to minimum feature dimensions
Return new aperture if required, else return False
"""
dimx = dimy = None
# Check for X and Y dimensions less than minimum
if (self.dimx != None) and (self.dimx < minimum):
dimx = minimum
if (self.dimy != None) and (self.dimx < minimum):
dimy = minimum
# Return new aperture if needed
if (dimx != None) or (dimy != None):
if dimx==None: dimx=self.dimx
if dimy==None: dimy=self.dimy
return Aperture( (self.apname, self.pat, self.format), self.code, dimx, dimy )
else:
return False ## no new aperture needs to be created
def rotate(self, RevGAMT):
if self.apname in ('Macro',):
# Construct a rotated macro, see if it's in the GAMT, and set self.dimx
# to its name if so. If not, add the rotated macro to the GAMT and set
# self.dimx to the new name. Recall that GAMT maps name to macro
# (e.g., GAMT['M9'] = ApertureMacro(...)) while RevGAMT maps hash to
# macro name (e.g., RevGAMT[hash] = 'M9')
AMR = config.GAMT[self.dimx].rotated()
hash = AMR.hash()
try:
self.dimx = RevGAMT[hash]
except KeyError:
AMR = amacro.addToApertureMacroTable(AMR) # adds to GAMT and modifies name to global name
self.dimx = RevGAMT[hash] = AMR.name
elif self.dimy is not None: # Rectangles and Ovals have a dimy setting and need to be rotated
t = self.dimx
self.dimx = self.dimy
self.dimy = t
def rotated(self, RevGAMT):
# deepcopy doesn't work on re patterns for some reason so we copy ourselves manually
APR = Aperture((self.apname, self.pat, self.format), self.code, self.dimx, self.dimy)
APR.rotate(RevGAMT)
return APR
def dump(self, fid=sys.stdout):
fid.write(str(self))
def __str__(self):
return '%s: %s' % (self.code, self.hash())
#if 0:
# if self.dimy:
# return ('%s: %s (%.4f x %.4f)' % (self.code, self.apname, self.dimx, self.dimy))
# else:
# if self.apname in ('Macro'):
# return ('%s: %s (%s)' % (self.code, self.apname, self.dimx))
# else:
# return ('%s: %s (%.4f)' % (self.code, self.apname, self.dimx))
def hash(self):
if self.dimy:
return ('%s (%.5f x %.5f)' % (self.apname, self.dimx, self.dimy))
else:
if self.apname in ('Macro',):
return ('%s (%s)' % (self.apname, self.dimx))
else:
return ('%s (%.5f)' % (self.apname, self.dimx))
def writeDef(self, fid):
if self.dimy:
fid.write(self.format % (self.code, self.dimx, self.dimy))
else:
fid.write(self.format % (self.code, self.dimx))
# Parse the aperture definition in line 's'. macroNames is an aperture macro dictionary
# that translates macro names local to this file to global names in the GAMT. We make
# the translation right away so that the return value from this function is an aperture
# definition with a global macro name, e.g., 'ADD10M5'
def parseAperture(s, knownMacroNames):
for ap in Apertures:
match = ap[1].match(s)
if match:
dimy = None
if ap[0] in ('Circle', 'Octagon', 'Macro'):
code, dimx = match.groups()
else:
code, dimx, dimy = match.groups()
if ap[0] in ('Macro',):
if knownMacroNames.has_key(dimx):
dimx = knownMacroNames[dimx] # dimx is now GLOBAL, permanent macro name (e.g., 'M2')
else:
raise RuntimeError, 'Aperture Macro name "%s" not defined' % dimx
else:
try:
dimx = float(dimx)
if dimy:
dimy = float(dimy)
except:
raise RuntimeError, "Illegal floating point aperture size"
return Aperture(ap, code, dimx, dimy)
return None
# This function returns a dictionary where each key is an
# aperture code string (e.g., "D11") and the value is the
# Aperture object that represents it. For example:
#
# %ADD12R,0.0630X0.0630*%
#
# from a Gerber file would result in the dictionary entry:
#
# "D12": Aperture(ap, 'D10', 0.063, 0.063)
#
# The input fileList is a list of pathnames which will be read to construct the
# aperture table for a job. All the files in the given list will be so
# examined, and a global aperture table will be constructed as a dictionary.
# Same goes for the global aperture macro table.
tool_pat = re.compile(r'^(?:G54)?D\d+\*$')
def constructApertureTable(fileList):
# First we construct a dictionary where each key is the
# string representation of the aperture. Then we go back and assign
# numbers. For aperture macros, we construct their final version
# (i.e., 'M1', 'M2', etc.) right away, as they are parsed. Thus,
# we translate from 'THX10N' or whatever to 'M2' right away.
GAT = config.GAT # Global Aperture Table
GAT.clear()
GAMT = config.GAMT # Global Aperture Macro Table
GAMT.clear()
RevGAMT = {} # Dictionary keyed by aperture macro hash and returning macro name
AT = {} # Aperture Table for this file
for fname in fileList:
#print 'Reading apertures from %s ...' % fname
knownMacroNames = {}
fid = file(fname,'rt')
for line in fid:
# Get rid of CR
line = line.replace('\x0D', '')
if tool_pat.match(line):
break # When tools start, no more apertures are being defined
# If this is an aperture macro definition, add its string
# representation to the dictionary. It might already exist.
# Ignore %AMOC8* from Eagle for now as it uses a macro parameter.
if line[:7]=='%AMOC8*':
continue
# parseApertureMacro() sucks up all macro lines up to terminating '%'
AM = amacro.parseApertureMacro(line, fid)
if AM:
# Has this macro definition already been defined (perhaps by another name
# in another layer)?
try:
# If this macro has already been encountered anywhere in any job,
# RevGAMT will map the macro hash to the global macro name. Then,
# make the local association knownMacroNames[localMacroName] = globalMacroName.
knownMacroNames[AM.name] = RevGAMT[AM.hash()]
except KeyError:
# No, so define the global macro and do the translation. Note that
# addToApertureMacroTable() MODIFIES AM.name to the new M-name.
localMacroName = AM.name
AM = amacro.addToApertureMacroTable(AM)
knownMacroNames[localMacroName] = AM.name
RevGAMT[AM.hash()] = AM.name
else:
A = parseAperture(line, knownMacroNames)
# If this is an aperture definition, add the string representation
# to the dictionary. It might already exist.
if A:
AT[A.hash()] = A
fid.close()
# Now, go through and assign sequential codes to all apertures
code = 10
for val in AT.values():
key = 'D%d' % code
GAT[key] = val
val.code = key
code += 1
if 0:
keylist = config.GAT.keys()
keylist.sort()
print 'Apertures'
print '========='
for key in keylist:
print '%s' % config.GAT[key]
sys.exit(0)
def findHighestApertureCode(keys):
"Find the highest integer value in a list of aperture codes: ['D10', 'D23', 'D35', ...]"
# Must sort keys by integer value, not string since 99 comes before 100
# as an integer but not a string.
keys = [int(K[1:]) for K in keys]
keys.sort()
return keys[-1]
def addToApertureTable(AP):
GAT = config.GAT
lastCode = findHighestApertureCode(GAT.keys())
code = 'D%d' % (lastCode+1)
GAT[code] = AP
AP.code = code
return code
def findInApertureTable(AP):
"""Return 'D10', for example in response to query for an object
of type Aperture()"""
hash = AP.hash()
for key, val in config.GAT.items():
if hash==val.hash():
return key
return None
def findOrAddAperture(AP):
"""If the aperture exists in the GAT, modify the AP.code field to reflect the global code
and return the code. Otherwise, create a new aperture in the GAT and return the new code
for it."""
code = findInApertureTable(AP)
if code:
AP.code = code
return code
else:
return addToApertureTable(AP)
if __name__=="__main__":
constructApertureTable(sys.argv[1:])
keylist = config.GAMT.keys()
keylist.sort()
print 'Aperture Macros'
print '==============='
for key in keylist:
print '%s' % config.GAMT[key]
keylist = config.GAT.keys()
keylist.sort()
print 'Apertures'
print '========='
for key in keylist:
print '%s' % config.GAT[key]

399
gerbmerge/config.py Normal file
View File

@ -0,0 +1,399 @@
#!/usr/bin/env python
"""
Parse the GerbMerge configuration file.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import ConfigParser
import re
import string
import jobs
import aptable
# Configuration dictionary. Specify floats as strings. Ints can be specified
# as ints or strings.
Config = {
'xspacing': '0.125', # Spacing in horizontal direction
'yspacing': '0.125', # Spacing in vertical direction
'panelwidth': '12.6', # X-Dimension maximum panel size (Olimex)
'panelheight': '7.8', # Y-Dimension maximum panel size (Olimex)
'cropmarklayers': None, # e.g., *toplayer,*bottomlayer
'cropmarkwidth': '0.01', # Width (inches) of crop lines
'cutlinelayers': None, # as for cropmarklayers
'cutlinewidth': '0.01', # Width (inches) of cut lines
'minimumfeaturesize': 0, # Minimum dimension for selected layers
'toollist': None, # Name of file containing default tool list
'drillclustertolerance': '.002', # Tolerance for clustering drill sizes
'allowmissinglayers': 0, # Set to 1 to allow multiple jobs to have non-matching layers
'fabricationdrawingfile': None, # Name of file to which to write fabrication drawing, or None
'fabricationdrawingtext': None, # Name of file containing text to write to fab drawing
'excellondecimals': 4, # Number of digits after the decimal point in input Excellon files
'excellonleadingzeros': 0, # Generate leading zeros in merged Excellon output file
'outlinelayerfile': None, # Name of file to which to write simple box outline, or None
'scoringfile': None, # Name of file to which to write scoring data, or None
'leftmargin': 0, # Inches of extra room to leave on left side of panel for tooling
'topmargin': 0, # Inches of extra room to leave on top side of panel for tooling
'rightmargin': 0, # Inches of extra room to leave on right side of panel for tooling
'bottommargin': 0, # Inches of extra room to leave on bottom side of panel for tooling
'fiducialpoints': None, # List of X,Y co-ordinates at which to draw fiducials
'fiducialcopperdiameter': 0.08, # Diameter of copper part of fiducial
'fiducialmaskdiameter': 0.32, # Diameter of fiducial soldermask opening
}
# This dictionary is indexed by lowercase layer name and has as values a file
# name to use for the output.
MergeOutputFiles = {
'boardoutline': 'merged.boardoutline.ger',
'drills': 'merged.drills.xln',
'placement': 'merged.placement.txt',
'toollist': 'merged.toollist.drl'
}
# The global aperture table, indexed by aperture code (e.g., 'D10')
GAT = {}
# The global aperture macro table, indexed by macro name (e.g., 'M3', 'M4R' for rotated macros)
GAMT = {}
# The list of all jobs loaded, indexed by job name (e.g., 'PowerBoard')
Jobs = {}
# The set of all Gerber layer names encountered in all jobs. Doesn't
# include drills.
LayerList = {'boardoutline': 1}
# The tool list as read in from the DefaultToolList file in the configuration
# file. This is a dictionary indexed by tool name (e.g., 'T03') and
# a floating point number as the value, the drill diameter in inches.
DefaultToolList = {}
# The GlobalToolMap dictionary maps tool name to diameter in inches. It
# is initially empty and is constructed after all files are read in. It
# only contains actual tools used in jobs.
GlobalToolMap = {}
# The GlobalToolRMap dictionary is a reverse dictionary of ToolMap, i.e., it maps
# diameter to tool name.
GlobalToolRMap = {}
##############################################################################
# This configuration option determines whether trimGerber() is called
TrimGerber = 1
# This configuration option determines whether trimExcellon() is called
TrimExcellon = 1
# This configuration option determines the minimum size of feature dimensions for
# each layer. It is a dictionary indexed by layer name (e.g. '*topsilkscreen') and
# has a floating point number as the value (in inches).
MinimumFeatureDimension = {}
# This configuration option is a positive integer that determines the maximum
# amout of time to allow for random placements (seconds). A SearchTimeout of 0
# indicates that no timeout should occur and random placements will occur
# forever until a KeyboardInterrupt is raised.
SearchTimeout = 0
# Construct the reverse-GAT/GAMT translation table, keyed by aperture/aperture macro
# hash string. The value is the aperture code (e.g., 'D10') or macro name (e.g., 'M5').
def buildRevDict(D):
RevD = {}
for key,val in D.items():
RevD[val.hash()] = key
return RevD
def parseStringList(L):
"""Parse something like '*toplayer, *bottomlayer' into a list of names
without quotes, spaces, etc."""
if 0:
if L[0]=="'":
if L[-1] != "'":
raise RuntimeError, "Illegal configuration string '%s'" % L
L = L[1:-1]
elif L[0]=='"':
if L[-1] != '"':
raise RuntimeError, "Illegal configuration string '%s'" % L
L = L[1:-1]
# This pattern matches quotes at the beginning and end...quotes must match
quotepat = re.compile(r'^([' "'" '"' r']?)([^\1]*)\1$')
delimitpat = re.compile(r'[ \t]*[,;][ \t]*')
match = quotepat.match(L)
if match:
L = match.group(2)
return delimitpat.split(L)
# Parse an Excellon tool list file of the form
#
# T01 0.035in
# T02 0.042in
def parseToolList(fname):
TL = {}
try:
fid = file(fname, 'rt')
except Exception, detail:
raise RuntimeError, "Unable to open tool list file '%s':\n %s" % (fname, str(detail))
pat_in = re.compile(r'\s*(T\d+)\s+([0-9.]+)\s*in\s*')
pat_mm = re.compile(r'\s*(T\d+)\s+([0-9.]+)\s*mm\s*')
pat_mil = re.compile(r'\s*(T\d+)\s+([0-9.]+)\s*(?:mil)?')
for line in fid.xreadlines():
line = string.strip(line)
if (not line) or (line[0] in ('#', ';')): continue
mm = 0
mil = 0
match = pat_in.match(line)
if not match:
mm = 1
match = pat_mm.match(line)
if not match:
mil = 1
match = pat_mil.match(line)
if not match:
continue
#raise RuntimeError, "Illegal tool list specification:\n %s" % line
tool, size = match.groups()
try:
size = float(size)
except:
raise RuntimeError, "Tool size in file '%s' is not a valid floating-point number:\n %s" % (fname,line)
if mil:
size = size*0.001 # Convert mil to inches
elif mm:
size = size/25.4 # Convert mm to inches
# Canonicalize tool so that T1 becomes T01
tool = 'T%02d' % int(tool[1:])
if TL.has_key(tool):
raise RuntimeError, "Tool '%s' defined more than once in tool list file '%s'" % (tool,fname)
TL[tool]=size
fid.close()
return TL
# This function parses the job configuration file and does
# everything needed to:
#
# * parse global options and store them in the Config dictionary
# as natural types (i.e., ints, floats, lists)
#
# * Read Gerber/Excellon data and populate the Jobs dictionary
#
# * Read Gerber/Excellon data and populate the global aperture
# table, GAT, and the global aperture macro table, GAMT
#
# * read the tool list file and populate the DefaultToolList dictionary
def parseConfigFile(fname, Config=Config, Jobs=Jobs):
global DefaultToolList
CP = ConfigParser.ConfigParser()
CP.readfp(file(fname,'rt'))
# First parse global options
if CP.has_section('Options'):
for opt in CP.options('Options'):
# Is it one we expect
if Config.has_key(opt):
# Yup...override it
Config[opt] = CP.get('Options', opt)
elif CP.defaults().has_key(opt):
pass # Ignore DEFAULTS section keys
elif opt in ('fabricationdrawing', 'outlinelayer'):
print '*'*73
print '\nThe FabricationDrawing and OutlineLayer configuration options have been'
print 'renamed as of GerbMerge version 1.0. Please consult the documentation for'
print 'a description of the new options, then modify your configuration file.\n'
print '*'*73
sys.exit(1)
else:
raise RuntimeError, "Unknown option '%s' in [Options] section of configuration file" % opt
else:
raise RuntimeError, "Missing [Options] section in configuration file"
# Ensure we got a tool list
if not Config.has_key('toollist'):
raise RuntimeError, "INTERNAL ERROR: Missing tool list assignment in [Options] section"
# Make integers integers, floats floats
for key,val in Config.items():
try:
val = int(val)
Config[key]=val
except:
try:
val = float(val)
Config[key]=val
except:
pass
# Process lists of strings
if Config['cutlinelayers']:
Config['cutlinelayers'] = parseStringList(Config['cutlinelayers'])
if Config['cropmarklayers']:
Config['cropmarklayers'] = parseStringList(Config['cropmarklayers'])
# Process list of minimum feature dimensions
if Config['minimumfeaturesize']:
temp = Config['minimumfeaturesize'].split(",")
try:
for index in range(0, len(temp), 2):
MinimumFeatureDimension[ temp[index] ] = float( temp[index + 1] )
except:
raise RuntimeError, "Illegal configuration string:" + Config['minimumfeaturesize']
# Process MergeOutputFiles section to set output file names
if CP.has_section('MergeOutputFiles'):
for opt in CP.options('MergeOutputFiles'):
# Each option is a layer name and the output file for this name
if opt[0]=='*' or opt in ('boardoutline', 'drills', 'placement', 'toollist'):
MergeOutputFiles[opt] = CP.get('MergeOutputFiles', opt)
# Now, we go through all jobs and collect Gerber layers
# so we can construct the Global Aperture Table.
apfiles = []
for jobname in CP.sections():
if jobname=='Options': continue
if jobname=='MergeOutputFiles': continue
if jobname=='GerbMergeGUI': continue
# Ensure all jobs have a board outline
if not CP.has_option(jobname, 'boardoutline'):
raise RuntimeError, "Job '%s' does not have a board outline specified" % jobname
if not CP.has_option(jobname, 'drills'):
raise RuntimeError, "Job '%s' does not have a drills layer specified" % jobname
for layername in CP.options(jobname):
if layername[0]=='*' or layername=='boardoutline':
fname = CP.get(jobname, layername)
apfiles.append(fname)
if layername[0]=='*':
LayerList[layername]=1
# Now construct global aperture tables, GAT and GAMT. This step actually
# reads in the jobs for aperture data but doesn't store Gerber
# data yet.
aptable.constructApertureTable(apfiles)
del apfiles
if 0:
keylist = GAMT.keys()
keylist.sort()
for key in keylist:
print '%s' % GAMT[key]
sys.exit(0)
# Parse the tool list
if Config['toollist']:
DefaultToolList = parseToolList(Config['toollist'])
# Now get jobs. Each job implies layer names, and we
# expect consistency in layer names from one job to the
# next. Two reserved layer names, however, are
# BoardOutline and Drills.
Jobs.clear()
do_abort = 0
errstr = 'ERROR'
if Config['allowmissinglayers']:
errstr = 'WARNING'
for jobname in CP.sections():
if jobname=='Options': continue
if jobname=='MergeOutputFiles': continue
if jobname=='GerbMergeGUI': continue
print 'Reading data from', jobname, '...'
J = jobs.Job(jobname)
# Parse the job settings, like tool list, first, since we are not
# guaranteed to have ConfigParser return the layers in the same order that
# the user wrote them, and we may get Gerber files before we get a tool
# list! Same thing goes for ExcellonDecimals. We need to know what this is
# before parsing any Excellon files.
for layername in CP.options(jobname):
fname = CP.get(jobname, layername)
if layername == 'toollist':
J.ToolList = parseToolList(fname)
elif layername=='excellondecimals':
try:
J.ExcellonDecimals = int(fname)
except:
raise RuntimeError, "Excellon decimals '%s' in config file is not a valid integer" % fname
elif layername=='repeat':
try:
J.Repeat = int(fname)
except:
raise RuntimeError, "Repeat count '%s' in config file is not a valid integer" % fname
for layername in CP.options(jobname):
fname = CP.get(jobname, layername)
if layername=='boardoutline':
J.parseGerber(fname, layername, updateExtents=1)
elif layername[0]=='*':
J.parseGerber(fname, layername, updateExtents=0)
elif layername=='drills':
J.parseExcellon(fname)
# Emit warnings if some layers are missing
LL = LayerList.copy()
for layername in J.apxlat.keys():
assert LL.has_key(layername)
del LL[layername]
if LL:
if errstr=='ERROR':
do_abort=1
print '%s: Job %s is missing the following layers:' % (errstr, jobname)
for layername in LL.keys():
print ' %s' % layername
# Store the job in the global Jobs dictionary, keyed by job name
Jobs[jobname] = J
if do_abort:
raise RuntimeError, 'Exiting since jobs are missing layers. Set AllowMissingLayers=1\nto override.'
if __name__=="__main__":
CP = parseConfigFile(sys.argv[1])
print Config
sys.exit(0)
if 0:
for key, val in CP.defaults().items():
print '%s: %s' % (key,val)
for section in CP.sections():
print '[%s]' % section
for opt in CP.options(section):
print ' %s=%s' % (opt, CP.get(section, opt))

208
gerbmerge/drillcluster.py Normal file
View File

@ -0,0 +1,208 @@
#!/usr/bin/env python
"""
Drill clustering routines to reduce total number of drills and remap
drilling commands to the new reduced drill set.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
_STATUS = True ## indicates status messages should be shown
_DEBUG = False ## indicates debug and status messages should be shown
def cluster(drills, tolerance, debug = _DEBUG):
"""
Take a dictionary of drill names and sizes and cluster them
A tolerance of 0 will effectively disable clustering
Returns clustered drill dictionary
"""
global _DEBUG
_DEBUG = debug
clusters = []
debug_print("\n " + str( len(drills) ) + " Original drills:")
debug_print( drillsToString(drills) )
debug_print("Clustering drill sizes ...", True)
# Loop through all drill sizes
sizes = drills.keys()
sizes.sort()
for size in sizes:
match = False
# See if size fits into any current clusters, else make new cluster
for index in range( len(clusters) ):
c = clusters[index]
if not len(c):
break
mn = min(c)
mx = max(c)
##debug_print( "Validating " + str_d(size) + " in " + str_d(c) )
##debug_print( "Possible cluster range = " + str_d(mx - 2 * tolerance) + " to " + str_d(mn + 2 * tolerance) )
if (size >= mx - 2 * tolerance) and (size <= mn + 2 * tolerance):
debug_print( str_d(size) + " belongs with " + str_d(c) )
clusters[index].append(size)
match = True
break
if not match:
debug_print(str_d(size) + " belongs in a new cluster")
clusters.append( [size] )
debug_print("\n Creating new drill dictionary ...")
new_drills = {}
tool_num = 0
# Create new dictionary of clustered drills
for c in clusters:
tool_num += 1
new_drill = "T%02d" % tool_num
c.sort()
new_size = ( min(c) + max(c) ) / 2.0
new_drills[new_size] = new_drill
debug_print(str_d(c) + " will be represented by " + new_drill + " (" + str_d(new_size) + ")")
debug_print("\n " + str( len(new_drills) ) + " Clustered Drills:")
debug_print( drillsToString(new_drills) )
debug_print("Drill count reduced from " + str( len(drills) ) + " to " + str( len(new_drills) ), True)
return new_drills
def remap(jobs, globalToolMap, debug = _DEBUG):
"""
Remap tools and commands in all jobs to match new tool map
Returns None
"""
# Set global variables from parameters
global _DEBUG
_DEBUG = debug
debug_print("Remapping tools and commands ...", True)
for job in jobs:
job = job.job ##access job inside job layout
debug_print("\n Job name: " + job.name)
debug_print("\n Original job tools:")
debug_print( str(job.xdiam) )
debug_print("\n Original commands:")
debug_print( str(job.xcommands) )
new_tools = {}
new_commands = {}
for tool, diam in job.xdiam.items():
##debug_print("\n Current tool: " + tool + " (" + str_d(diam) + ")")
# Search for best matching tool
best_diam, best_tool = globalToolMap[0]
for glob_diam, glob_tool in globalToolMap:
if abs(glob_diam - diam) < abs(best_diam - diam):
best_tool = glob_tool
best_diam = glob_diam
##debug_print("Best match: " + best_tool + " (" + str_d(best_diam) + ")")
new_tools[best_tool] = best_diam
##debug_print(best_tool + " will replace " + tool)
# Append commands to existing commands if they exist
if best_tool in new_commands:
##debug_print( "Current commands: " + str( new_commands[best_tool] ) )
temp = new_commands[best_tool]
temp.extend( job.xcommands[tool] )
new_commands[best_tool] = temp
##debug_print( "All commands: " + str( new_commands[best_tool] ) )
else:
new_commands[best_tool] = job.xcommands[tool]
debug_print("\n New job tools:")
debug_print( str(new_tools) )
debug_print("\n New commands:")
debug_print( str(new_commands) )
job.xdiam = new_tools
job.xcommands = new_commands
def debug_print(text, status = False, newLine = True):
"""
Print debugging statemetns
Returs None, Printts text
"""
if _DEBUG or (status and _STATUS):
if newLine:
print " ", text
else:
print " ", text,
def str_d(drills):
"""
Format drill sizes for printing debug and status messages
Returns drills as formatted string
"""
string = ""
try:
len(drills)
except:
string = "%.4f" % drills
else:
string = "["
for drill in drills:
string += ( "%.4f" % drill + ", ")
string = string[:len(string) - 2] + "]"
return string
def drillsToString(drills):
"""
Format drill dictionary for printing debug and status messages
Returns drills as formatted string
"""
string = ""
drills = drills.items()
drills.sort()
for size, drill in drills:
string += drill + " = " + str_d(size) + "\n "
return string
"""
The following code runs test drill clusterings with random drill sets.
"""
if __name__=="__main__":
import random
print " Clustering random drills..."
old = {}
tool_num = 0
while len(old) < 99:
rand_size = round(random.uniform(.02, .04), 4)
if rand_size in old:
continue
tool_num += 1
old[rand_size] = "T%02d" % tool_num
new = cluster(old, .0003, True)

210
gerbmerge/fabdrawing.py Normal file
View File

@ -0,0 +1,210 @@
#!/usr/bin/env python
"""This file handles the writing of the fabrication drawing Gerber file
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import string
import config
import makestroke
import util
def writeDrillHits(fid, Place, Tools):
toolNumber = -1
for tool in Tools:
toolNumber += 1
try:
size = config.GlobalToolMap[tool]
except:
raise RuntimeError, "INTERNAL ERROR: Tool code %s not found in global tool list" % tool
#for row in Layout:
# row.writeDrillHits(fid, size, toolNumber)
for job in Place.jobs:
job.writeDrillHits(fid, size, toolNumber)
def writeBoundingBox(fid, OriginX, OriginY, MaxXExtent, MaxYExtent):
x = util.in2gerb(OriginX)
y = util.in2gerb(OriginY)
X = util.in2gerb(MaxXExtent)
Y = util.in2gerb(MaxYExtent)
makestroke.drawPolyline(fid, [(x,y), (X,y), (X,Y), (x,Y), (x,y)], 0, 0)
def writeDrillLegend(fid, Tools, OriginY, MaxXExtent):
# This is the spacing from the right edge of the board to where the
# drill legend is to be drawn, in inches. Remember we have to allow
# for dimension arrows, too.
dimspace = 0.5 # inches
# This is the spacing from the drill hit glyph to the drill size
# in inches.
glyphspace = 0.1 # inches
# Convert to Gerber 2.5 units
dimspace = util.in2gerb(dimspace)
glyphspace = util.in2gerb(glyphspace)
# Construct a list of tuples (toolSize, toolNumber) where toolNumber
# is the position of the tool in Tools and toolSize is in inches.
L = []
toolNumber = -1
for tool in Tools:
toolNumber += 1
L.append((config.GlobalToolMap[tool], toolNumber))
# Now sort the list from smallest to largest
L.sort()
# And reverse to go from largest to smallest, so we can write the legend
# from the bottom up
L.reverse()
# For each tool, draw a drill hit marker then the size of the tool
# in inches.
posY = util.in2gerb(OriginY)
posX = util.in2gerb(MaxXExtent) + dimspace
maxX = 0
for size,toolNum in L:
# Determine string to write and midpoint of string
s = '%.3f"' % size
ll, ur = makestroke.boundingBox(s, posX+glyphspace, posY) # Returns lower-left point, upper-right point
midpoint = (ur[1]+ll[1])/2
# Keep track of maximum extent of legend
maxX = max(maxX, ur[0])
makestroke.drawDrillHit(fid, posX, midpoint, toolNum)
makestroke.writeString(fid, s, posX+glyphspace, posY, 0)
posY += int(round((ur[1]-ll[1])*1.5))
# Return value is lower-left of user text area, without any padding.
return maxX, util.in2gerb(OriginY)
def writeDimensionArrow(fid, OriginX, OriginY, MaxXExtent, MaxYExtent):
x = util.in2gerb(OriginX)
y = util.in2gerb(OriginY)
X = util.in2gerb(MaxXExtent)
Y = util.in2gerb(MaxYExtent)
# This constant is how far away from the board the centerline of the dimension
# arrows should be, in inches.
dimspace = 0.2
# Convert it to Gerber (0.00001" or 2.5) units
dimspace = util.in2gerb(dimspace)
# Draw an arrow above the board, on the left side and right side
makestroke.drawDimensionArrow(fid, x, Y+dimspace, makestroke.FacingLeft)
makestroke.drawDimensionArrow(fid, X, Y+dimspace, makestroke.FacingRight)
# Draw arrows to the right of the board, at top and bottom
makestroke.drawDimensionArrow(fid, X+dimspace, Y, makestroke.FacingUp)
makestroke.drawDimensionArrow(fid, X+dimspace, y, makestroke.FacingDown)
# Now draw the text. First, horizontal text above the board.
s = '%.3f"' % (MaxXExtent - OriginX)
ll, ur = makestroke.boundingBox(s, 0, 0)
s_width = ur[0]-ll[0] # Width in 2.5 units
s_height = ur[1]-ll[1] # Height in 2.5 units
# Compute the position in 2.5 units where we should draw this. It should be
# centered horizontally and also vertically about the dimension arrow centerline.
posX = x + (x+X)/2
posX -= s_width/2
posY = Y + dimspace - s_height/2
makestroke.writeString(fid, s, posX, posY, 0)
# Finally, draw the extending lines from the text to the arrows.
posY = Y + dimspace
posX1 = posX - util.in2gerb(0.1) # 1000
posX2 = posX + s_width + util.in2gerb(0.1) # 1000
makestroke.drawLine(fid, x, posY, posX1, posY)
makestroke.drawLine(fid, posX2, posY, X, posY)
# Now do the vertical text
s = '%.3f"' % (MaxYExtent - OriginY)
ll, ur = makestroke.boundingBox(s, 0, 0)
s_width = ur[0]-ll[0]
s_height = ur[1]-ll[1]
# As above, figure out where to draw this. Rotation will be -90 degrees
# so new origin will be top-left of bounding box after rotation.
posX = X + dimspace - s_height/2
posY = y + (y+Y)/2
posY += s_width/2
makestroke.writeString(fid, s, posX, posY, -90)
# Draw extending lines
posX = X + dimspace
posY1 = posY + util.in2gerb(0.1) # 1000
posY2 = posY - s_width - util.in2gerb(0.1) # 1000
makestroke.drawLine(fid, posX, Y, posX, posY1)
makestroke.drawLine(fid, posX, posY2, posX, y)
def writeUserText(fid, X, Y):
fname = config.Config['fabricationdrawingtext']
if not fname: return
try:
tfile = file(fname, 'rt')
except Exception, detail:
raise RuntimeError, "Could not open fabrication drawing text file '%s':\n %s" % (fname,str(detail))
lines = tfile.readlines()
tfile.close()
lines.reverse() # We're going to print from bottom up
# Offset X position to give some clearance from drill legend
X += util.in2gerb(0.2) # 2000
for line in lines:
# Get rid of CR
line = string.replace(line, '\x0D', '')
# Chop off \n
#if line[-1] in string.whitespace:
# line = line[:-1]
# Strip off trailing whitespace
line = string.rstrip(line)
# Blank lines still need height, so must have at least one character
if not line:
line = ' '
ll, ur = makestroke.boundingBox(line, X, Y)
makestroke.writeString(fid, line, X, Y, 0)
Y += int(round((ur[1]-ll[1])*1.5))
# Main entry point. Gerber file has already been opened, header written
# out, 1mil tool selected.
def writeFabDrawing(fid, Place, Tools, OriginX, OriginY, MaxXExtent, MaxYExtent):
# Write out all the drill hits
writeDrillHits(fid, Place, Tools)
# Draw a bounding box for the project
writeBoundingBox(fid, OriginX, OriginY, MaxXExtent, MaxYExtent)
# Write out the drill hit legend off to the side. This function returns
# (X,Y) lower-left origin where user text is to begin, in Gerber units
# and without any padding.
X,Y = writeDrillLegend(fid, Tools, OriginY, MaxXExtent)
# Write out the dimensioning arrows
writeDimensionArrow(fid, OriginX, OriginY, MaxXExtent, MaxYExtent)
# Finally, write out user text
writeUserText(fid, X, Y)

346
gerbmerge/geometry.py Normal file
View File

@ -0,0 +1,346 @@
#!/usr/bin/env python
"""
General geometry support routines.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import math
# Ensure all list elements are unique
def uniqueify(L):
return {}.fromkeys(L).keys()
# This function rounds an (X,Y) point to integer co-ordinates
def roundPoint(pt):
return (int(round(pt[0])),int(round(pt[1])))
# Returns True if the segment defined by endpoints p1 and p2 is vertical
def isSegmentVertical(p1, p2):
return p1[0]==p2[0]
# Returns True if the segment defined by endpoints p1 and p2 is horizontal
def isSegmentHorizontal(p1, p2):
return p1[1]==p2[1]
# Returns slope of a non-vertical line segment
def segmentSlope(p1, p2):
return float(p2[1]-p1[1])/(p2[0]-p1[0])
# Determine if the (X,Y) 'point' is on the line segment defined by endpoints p1
# and p2, both (X,Y) tuples. It's assumed that the point is on the line defined
# by the segment, but just may be beyond the endpoints. NOTE: No testing is
# performed to see if the point is actually on the line defined by the segment!
# This is assumed!
def isPointOnSegment(point, p1, p2):
if isSegmentVertical(p1,p2):
# Treat vertical lines by comparing Y-ordinates
return (point[1]-p2[1])*(point[1]-p1[1]) <= 0
else:
# Treat other lines, including horizontal lines, by comparing X-ordinates
return (point[0]-p2[0])*(point[0]-p1[0]) <= 0
# Returns (X,Y) point where the line segment defined by (X,Y) endpoints p1 and
# p2 intersects the line segment defined by endpoints q1 and q2. Only a single
# intersection point is allowed, so no coincident lines. If there is no point
# of intersection, None is returned.
def segmentXsegment1pt(p1, p2, q1, q2):
A,B = p1
C,D = p2
P,Q = q1
R,S = q2
# We have to consider special cases of one or other line segments being vertical
if isSegmentVertical(p1,p2):
if isSegmentVertical(q1,q2): return None
x = A
y = segmentSlope(q1,q2)*(A-P) + Q
elif isSegmentVertical(q1,q2):
x = P
y = segmentSlope(p1,p2)*(P-A) + B
else:
m1 = segmentSlope(p1,p2)
m2 = segmentSlope(q1,q2)
if m1==m2: return None
x = (A*m1 - B - P*m2 + Q) / (m1-m2)
y = m1*(x-A) + B
# Candidate point identified. Check to make sure it's on both line segments.
if isPointOnSegment((x,y), p1, p2) and isPointOnSegment((x,y), q1, q2):
return roundPoint((x,y))
else:
return None
# Returns True if the given (X,Y) 'point' is strictly within the rectangle
# defined by (LLX,LLY,URX,URY) co-ordinates (LL=lower left, UR=upper right).
def isPointStrictlyInRectangle(point, rect):
x,y = point
llx,lly,urx,ury = rect
return (llx < x < urx) and (lly < y < ury)
# This function takes two points which define the extents of a rectangle. The
# return value is a 5-tuple (ll, ul, ur, lr, rect) which comprises 4 points
# (lower-left, upper-left, upper-right, lower-right) and a rect object (minx,
# miny, maxx, maxy). If called with a single argument, it is expected to be
# a 4-tuple (x1,y1,x2,y2).
def canonicalizeExtents(pt1, pt2=None):
# First canonicalize lower-left and upper-right points
if pt2 is None:
maxx = max(pt1[0], pt1[2])
minx = min(pt1[0], pt1[2])
maxy = max(pt1[1], pt1[3])
miny = min(pt1[1], pt1[3])
else:
maxx = max(pt1[0], pt2[0])
minx = min(pt1[0], pt2[0])
maxy = max(pt1[1], pt2[1])
miny = min(pt1[1], pt2[1])
# Construct the four corners
llpt = (minx,miny)
urpt = (maxx,maxy)
ulpt = (minx,maxy)
lrpt = (maxx,miny)
# Construct a rect object for use by various functions
rect = (minx, miny, maxx, maxy)
return (llpt, ulpt, urpt, lrpt, rect)
# This function returns a list of intersection points of the line segment
# pt1-->pt2 and the box defined by corners llpt and urpt. These corners are
# canonicalized internally so they need not necessarily be lower-left and
# upper-right points.
#
# The return value may be a list of 0, 1, or 2 points. If the list has 2
# points, then the segment intersects the box in two points since both points
# are outside the box. If the list has 1 point, then the segment has one point
# inside the box and another point outside. If the list is empty, the segment
# has both points outside the box and there is no intersection, or has both
# points inside the box.
#
# Note that segments collinear with box edges produce no points of
# intersection.
def segmentXbox(pt1, pt2, llpt, urpt):
# First canonicalize lower-left and upper-right points
llpt, ulpt, urpt, lrpt, rect = canonicalizeExtents(llpt, urpt)
# Determine whether one point is inside the rectangle and the other is not.
# Note the XOR operator '^'
oneInOneOut = isPointStrictlyInRectangle(pt1,rect) ^ isPointStrictlyInRectangle(pt2,rect)
# Find all intersections of the segment with the 4 sides of the box,
# one side at a time. L will be the list of definitely-true intersections,
# while corners is a list of potential intersections. An intersection
# is potential if a) it is a corner, and b) there is another intersection
# of the line with the box somewhere else. This is how we handle
# corner intersections, which are sometimes legal (when one segment endpoint
# is inside the box and the other isn't, or when the segment intersects the
# box in two places) and sometimes not (when the segment is "tangent" to
# the box at the corner and the corner is the signle point of intersection).
L = []
corners = []
# Do not allow intersection if segment is collinear with box sides. For
# example, a horizontal line collinear with the box top side should not
# return an intersection with the upper-left or upper-right corner.
# Similarly, a point of intersection that is a corner should only be
# allowed if one segment point is inside the box and the other is not,
# otherwise it means the segment is "tangent" to the box at that corner.
# There is a case, however, in which a corner is a point of intersection
# with both segment points outside the box, and that is if there are two
# points of intersection, i.e., the segment goes completely through the box.
def checkIntersection(corner1, corner2):
# Check intersection with side of box
pt = segmentXsegment1pt(pt1, pt2, corner1, corner2)
if pt in (corner1,corner2):
# Only allow this corner intersection point if line is not
# horizontal/vertical and one point is inside rectangle while other is
# not, or the segment intersects the box in two places. Since oneInOneOut
# calls isPointStrictlyInRectangle(), which automatically excludes points
# on the box itself, horizontal/vertical lines collinear with box sides
# will always lead to oneInOneOut==False (since both will be "out of
# box").
if oneInOneOut:
L.append(pt)
else:
corners.append(pt) # Potentially a point of intersection...we'll have to wait and
# see if there is one more point of intersection somewhere else.
else:
# Not a corner intersection, so it's valid
if pt is not None: L.append(pt)
# Check intersection with left side of box
checkIntersection(llpt, ulpt)
# Check intersection with top side of box
checkIntersection(ulpt, urpt)
# Check intersection with right side of box
checkIntersection(urpt, lrpt)
# Check intersection with bottom side of box
checkIntersection(llpt, lrpt)
# Ensure all points are unique. We may get a double hit at the corners
# of the box.
L = uniqueify(L)
corners = uniqueify(corners)
# If the total number of intersections len(L)+len(corners) is 2, the corner
# is valid. If there is only a single corner, it's a tangent and invalid.
# However, if both corners are on the same side of the box, it's not valid.
numPts = len(L)+len(corners)
assert numPts <= 2
if numPts == 2:
if len(corners)==2 and (isSegmentHorizontal(corners[0], corners[1]) or isSegmentVertical(corners[0],corners[1])):
return []
else:
L += corners
L.sort() # Just for stability in assertion checking
return L
else:
L.sort()
return L # Correct if numPts==1, since it will be empty or contain a single valid intersection
# Correct if numPts==0, since it will be empty
# This function determines if two rectangles defined by 4-tuples
# (minx, miny, maxx, maxy) have any rectangle in common. If so, it is
# returned as a 4-tuple, else None is returned. This function assumes
# the rectangles are canonical so that minx<maxx, miny<maxy. If the
# optional allowLines parameter is True, rectangles that overlap on
# a line are considered overlapping, otherwise they must overlap with
# a rectangle of at least width 1.
def areExtentsOverlapping(E1, E2, allowLines=False):
minX,minY,maxX,maxY = E1
minU,minV,maxU,maxV = E2
if allowLines:
if (minU > maxX) or (maxU < minX) or (minV > maxY) or (maxV < minY):
return False
else:
return True
else:
if (minU >= maxX) or (maxU <= minX) or (minV >= maxY) or (maxV <= minY):
return False
else:
return True
# Compute the intersection of two rectangles defined by 4-tuples E1 and E2,
# which are not necessarily canonicalized.
def intersectExtents(E1, E2):
ll1, ul1, ur1, lr1, rect1 = canonicalizeExtents(E1)
ll2, ul2, ur2, lr2, rect2 = canonicalizeExtents(E2)
if not areExtentsOverlapping(rect1, rect2):
return None
xll = max(rect1[0], rect2[0]) # Maximum of minx values
yll = max(rect1[1], rect2[1]) # Maximum of miny values
xur = min(rect1[2], rect2[2]) # Minimum of maxx values
yur = min(rect1[3], rect2[3]) # Minimum of maxy values
return (xll, yll, xur, yur)
# This function returns True if rectangle E1 is wholly contained within
# rectangle E2. Both E1 and E2 are 4-tuples (minx,miny,maxx,maxy), not
# necessarily canonicalized. This function is like a slightly faster
# version of "intersectExtents(E1, E2)==E1".
def isRect1InRect2(E1, E2):
ll1, ul1, ur1, lr1, rect1 = canonicalizeExtents(E1)
ll2, ul2, ur2, lr2, rect2 = canonicalizeExtents(E2)
return (ll1[0] >= ll2[0]) and (ll1[1] >= ll2[1]) \
and (ur1[0] <= ur2[0]) and (ur1[1] <= ur2[1])
# Return width of rectangle, which may be 0 if bottom-left and upper-right X
# positions are the same. The rectangle is a 4-tuple (minx,miny,maxx,maxy).
def rectWidth(rect):
return abs(rect[2]-rect[0])
# Return height of rectangle, which may be 0 if bottom-left and upper-right Y
# positions are the same. The rectangle is a 4-tuple (minx,miny,maxx,maxy).
def rectHeight(rect):
return abs(rect[3]-rect[1])
# Return center (X,Y) co-ordinates of rectangle.
def rectCenter(rect):
dx = rectWidth(rect)
dy = rectHeight(rect)
if dx & 1: # Odd width: center is (left+right)/2 + 1/2
X = (rect[0] + rect[2] + 1)/2
else: # Even width: center is (left+right)/2
X = (rect[0] + rect[2])/2
if dy & 1:
Y = (rect[1] + rect[3] + 1)/2
else:
Y = (rect[1] + rect[3])/2
return (X,Y)
if __name__=="__main__":
llpt = (1000,1000)
urpt = (5000,5000)
# A segment that cuts across the box and intersects in corners
assert segmentXbox((0,0), (6000,6000), llpt, urpt) == [(1000,1000), (5000,5000)] # Two valid corners
assert segmentXbox((0,6000), (6000,0), llpt, urpt) == [(1000,5000), (5000,1000)] # Two valid corners
assert segmentXbox((500,500), (2500, 2500), llpt, urpt) == [(1000,1000)] # One valid corner
assert segmentXbox((2500,2500), (5500, 5500), llpt, urpt) == [(5000,5000)] # One valid corner
# Segments collinear with box sides
assert segmentXbox((1000,0), (1000,6000), llpt, urpt) == [] # Box side contained in segment
assert segmentXbox((1000,0), (1000,3000), llpt, urpt) == [] # Box side partially overlaps segment
assert segmentXbox((1000,2000), (1000,4000), llpt, urpt) == [] # Segment contained in box side
# Segments fully contained within box
assert segmentXbox((1500,2000), (2000,2500), llpt, urpt) == []
# Segments with points on box sides
assert segmentXbox((2500,1000), (2700,1200), llpt, urpt) == [(2500,1000)] # One point on box side
assert segmentXbox((2500,1000), (2700,5000), llpt, urpt) == [(2500,1000), (2700,5000)] # Two points on box sides
# Segment intersects box at one point
assert segmentXbox((3500,5500), (3000, 2500), llpt, urpt) == [(3417, 5000)] # First point outside
assert segmentXbox((3500,1500), (3000, 6500), llpt, urpt) == [(3150, 5000)] # Second point outside
# Segment intersects box at two points, not corners
assert segmentXbox((500,3000), (1500,500), llpt, urpt) == [(1000,1750), (1300,1000)]
assert segmentXbox((2500,300), (5500,3500), llpt, urpt) == [(3156,1000), (5000,2967)]
assert segmentXbox((5200,1200), (2000,6000), llpt, urpt) == [(2667,5000), (5000, 1500)]
assert segmentXbox((3200,5200), (-10, 1200), llpt, urpt) == [(1000, 2459), (3040, 5000)]
assert segmentXbox((500,2000), (5500, 2000), llpt, urpt) == [(1000,2000), (5000, 2000)]
assert segmentXbox((5200,1250), (-200, 4800), llpt, urpt) == [(1000, 4011), (5000, 1381)]
assert segmentXbox((1300,200), (1300, 5200), llpt, urpt) == [(1300, 1000), (1300, 5000)]
assert segmentXbox((1200,200), (1300, 5200), llpt, urpt) == [(1216, 1000), (1296, 5000)]
assert intersectExtents( (100,100,500,500), (500,500,900,900) ) == None
assert intersectExtents( (100,100,500,500), (400,400,900,900) ) == (400,400,500,500)
assert intersectExtents( (100,100,500,500), (200,0,600,300) ) == (200,100,500,300)
assert intersectExtents( (100,100,500,500), (200,0,300,600) ) == (200,100,300,500)
assert intersectExtents( (100,100,500,500), (0,600,50,550) ) == None
assert intersectExtents( (100,100,500,500), (0,600,600,-10) ) == (100,100,500,500)
assert intersectExtents( (100,100,500,500), (0,600,600,200) ) == (100,200,500,500)
assert intersectExtents( (100,100,500,500), (0,600,300,300) ) == (100,300,300,500)
assert isRect1InRect2( (100,100,500,500), (0,600,50,550) ) == False
assert isRect1InRect2( (100,100,500,500), (0,600,600,-10) ) == True
assert isRect1InRect2( (100,100,500,500), (0,600,600,200) ) == False
assert isRect1InRect2( (100,100,500,500), (0,600,300,300) ) == False
assert isRect1InRect2( (100,100,500,500), (0,0,500,500) ) == True
print 'All tests pass'

753
gerbmerge/gerbmerge.py Normal file
View File

@ -0,0 +1,753 @@
#!/usr/bin/env python
"""
Merge several RS274X (Gerber) files generated by Eagle into a single
job.
This program expects that each separate job has at least three files:
- a board outline (RS274X)
- data layers (copper, silkscreen, etc. in RS274X format)
- an Excellon drill file
Furthermore, it is expected that each job was generated by Eagle
using the GERBER_RS274X plotter, except for the drill file which
was generated by the EXCELLON plotter.
This program places all jobs into a single job.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import os
import getopt
import re
import aptable
import jobs
import config
import parselayout
import fabdrawing
import strokes
import tiling
import tilesearch1
import tilesearch2
import placement
import schwartz
import util
import scoring
import drillcluster
VERSION_MAJOR=1
VERSION_MINOR=8
RANDOM_SEARCH = 1
EXHAUSTIVE_SEARCH = 2
FROM_FILE = 3
config.AutoSearchType = RANDOM_SEARCH
config.RandomSearchExhaustiveJobs = 2
config.PlacementFile = None
# This is a handle to a GUI front end, if any, else None for command-line usage
GUI = None
def usage():
print \
"""
Usage: gerbmerge [Options] configfile [layoutfile]
Options:
-h, --help -- This help summary
-v, --version -- Program version and contact information
--random-search -- Automatic placement using random search (default)
--full-search -- Automatic placement using exhaustive search
--place-file=fn -- Read placement from file
--rs-fsjobs=N -- When using random search, exhaustively search N jobs
for each random placement (default: N=2)
--search-timeout=T -- When using random search, search for T seconds for best
random placement (default: T=0, search until stopped)
--no-trim-gerber -- Do not attempt to trim Gerber data to extents of board
--no-trim-excellon -- Do not attempt to trim Excellon data to extents of board
--octagons=fmt -- Generate octagons in two different styles depending on
the value of 'fmt':
fmt is 'rotate' : 0.0 rotation
fmt is 'normal' : 22.5 rotation (default)
If a layout file is not specified, automatic placement is performed. If the
placement is read from a file, then no automatic placement is performed and
the layout file (if any) is ignored.
NOTE: The dimensions of each job are determined solely by the maximum extent of
the board outline layer for each job.
"""
sys.exit(1)
def writeGerberHeader22degrees(fid):
fid.write( \
"""G75*
G70*
%OFA0B0*%
%FSLAX25Y25*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
""")
def writeGerberHeader0degrees(fid):
fid.write( \
"""G75*
G70*
%OFA0B0*%
%FSLAX25Y25*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,0.0*
%
""")
writeGerberHeader = writeGerberHeader22degrees
def writeApertureMacros(fid, usedDict):
keys = config.GAMT.keys()
keys.sort()
for key in keys:
if key in usedDict:
config.GAMT[key].writeDef(fid)
def writeApertures(fid, usedDict):
keys = config.GAT.keys()
keys.sort()
for key in keys:
if key in usedDict:
config.GAT[key].writeDef(fid)
def writeGerberFooter(fid):
fid.write('M02*\n')
def writeExcellonHeader(fid):
fid.write('%\n')
def writeExcellonFooter(fid):
fid.write('M30\n')
def writeExcellonTool(fid, tool, size):
fid.write('%sC%f\n' % (tool, size))
def writeFiducials(fid, drawcode, OriginX, OriginY, MaxXExtent, MaxYExtent):
"""Place fiducials at arbitrary points. The FiducialPoints list in the config specifies
sets of X,Y co-ordinates. Positive values of X/Y represent offsets from the lower left
of the panel. Negative values of X/Y represent offsets from the top right. So:
FiducialPoints = 0.125,0.125,-0.125,-0.125
means to put a fiducial 0.125,0.125 from the lower left and 0.125,0.125 from the top right"""
fid.write('%s*\n' % drawcode) # Choose drawing aperture
fList = config.Config['fiducialpoints'].split(',')
for i in range(0, len(fList), 2):
x,y = float(fList[i]), float(fList[i+1])
if x>=0:
x += OriginX
else:
x = MaxXExtent + x
if y>=0:
y += OriginX
else:
y = MaxYExtent + y
fid.write('X%07dY%07dD03*\n' % (util.in2gerb(x), util.in2gerb(y)))
def writeCropMarks(fid, drawing_code, OriginX, OriginY, MaxXExtent, MaxYExtent):
"""Add corner crop marks on the given layer"""
# Draw 125mil lines at each corner, with line edge right up against
# panel border. This means the center of the line is D/2 offset
# from the panel border, where D is the drawing line diameter.
fid.write('%s*\n' % drawing_code) # Choose drawing aperture
offset = config.GAT[drawing_code].dimx/2.0
# Lower-left
x = OriginX + offset
y = OriginY + offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.125), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.125)))
# Lower-right
x = MaxXExtent - offset
y = OriginY + offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.125)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x-0.125), util.in2gerb(y+0.000)))
# Upper-right
x = MaxXExtent - offset
y = MaxYExtent - offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x-0.125), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-0.125)))
# Upper-left
x = OriginX + offset
y = MaxYExtent - offset
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-0.125)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000)))
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.125), util.in2gerb(y+0.000)))
def disclaimer():
print """
****************************************************
* R E A D C A R E F U L L Y *
* *
* This program comes with no warranty. You use *
* this program at your own risk. Do not submit *
* board files for manufacture until you have *
* thoroughly inspected the output of this program *
* using a previewing program such as: *
* *
* Windows: *
* - GC-Prevue <http://www.graphicode.com> *
* - ViewMate <http://www.pentalogix.com> *
* *
* Linux: *
* - gerbv <http://gerbv.sourceforge.net> *
* *
* By using this program you agree to take full *
* responsibility for the correctness of the data *
* that is generated by this program. *
****************************************************
To agree to the above terms, press 'y' then Enter.
Any other key will exit the program.
"""
s = raw_input()
if s == 'y':
print
return
print "\nExiting..."
sys.exit(0)
def tile_jobs(Jobs):
"""Take a list of raw Job objects and find best tiling by calling tile_search"""
# We must take the raw jobs and construct a list of 4-tuples (Xdim,Ydim,job,rjob).
# This means we must construct a rotated job for each entry. We first sort all
# jobs from largest to smallest. This should give us the best tilings first so
# we can interrupt the tiling process and get a decent layout.
L = []
#sortJobs = schwartz.schwartz(Jobs, jobs.Job.jobarea)
sortJobs = schwartz.schwartz(Jobs, jobs.Job.maxdimension)
sortJobs.reverse()
for job in sortJobs:
Xdim = job.width_in()
Ydim = job.height_in()
rjob = jobs.rotateJob(job, 90) ##NOTE: This will only try 90 degree rotations though 180 & 270 are available
for count in range(job.Repeat):
L.append( (Xdim,Ydim,job,rjob) )
PX,PY = config.Config['panelwidth'],config.Config['panelheight']
if config.AutoSearchType==RANDOM_SEARCH:
tile = tilesearch2.tile_search2(L, PX, PY)
else:
tile = tilesearch1.tile_search1(L, PX, PY)
if not tile:
raise RuntimeError, 'Panel size %.2f"x%.2f" is too small to hold jobs' % (PX,PY)
return tile
def merge(opts, args, gui = None):
writeGerberHeader = writeGerberHeader22degrees
global GUI
GUI = gui
for opt, arg in opts:
if opt in ('--octagons',):
if arg=='rotate':
writeGerberHeader = writeGerberHeader0degrees
elif arg=='normal':
writeGerberHeader = writeGerberHeader22degrees
else:
raise RuntimeError, 'Unknown octagon format'
elif opt in ('--random-search',):
config.AutoSearchType = RANDOM_SEARCH
elif opt in ('--full-search',):
config.AutoSearchType = EXHAUSTIVE_SEARCH
elif opt in ('--rs-fsjobs',):
config.RandomSearchExhaustiveJobs = int(arg)
elif opt in ('--search-timeout',):
config.SearchTimeout = int(arg)
elif opt in ('--place-file',):
config.AutoSearchType = FROM_FILE
config.PlacementFile = arg
elif opt in ('--no-trim-gerber',):
config.TrimGerber = 0
elif opt in ('--no-trim-excellon',):
config.TrimExcellon = 0
else:
raise RuntimeError, "Unknown option: %s" % opt
if len(args) > 2 or len(args) < 1:
raise RuntimeError, 'Invalid number of arguments'
# Load up the Jobs global dictionary, also filling out GAT, the
# global aperture table and GAMT, the global aperture macro table.
updateGUI("Reading job files...")
config.parseConfigFile(args[0])
# Force all X and Y coordinates positive by adding absolute value of minimum X and Y
for name, job in config.Jobs.iteritems():
min_x, min_y = job.mincoordinates()
shift_x = shift_y = 0
if min_x < 0: shift_x = abs(min_x)
if min_y < 0: shift_y = abs(min_y)
if (shift_x > 0) or (shift_y > 0):
job.fixcoordinates( shift_x, shift_y )
# Display job properties
for job in config.Jobs.values():
print 'Job %s:' % job.name,
if job.Repeat > 1:
print '(%d instances)' % job.Repeat
else:
print
print ' Extents: (%d,%d)-(%d,%d)' % (job.minx,job.miny,job.maxx,job.maxy)
print ' Size: %f" x %f"' % (job.width_in(), job.height_in())
print
# Trim drill locations and flash data to board extents
if config.TrimExcellon:
updateGUI("Trimming Excellon data...")
print 'Trimming Excellon data to board outlines ...'
for job in config.Jobs.values():
job.trimExcellon()
if config.TrimGerber:
updateGUI("Trimming Gerber data...")
print 'Trimming Gerber data to board outlines ...'
for job in config.Jobs.values():
job.trimGerber()
# We start origin at (0.1", 0.1") just so we don't get numbers close to 0
# which could trip up Excellon leading-0 elimination.
OriginX = OriginY = 0.1
# Read the layout file and construct the nested list of jobs. If there
# is no layout file, do auto-layout.
updateGUI("Performing layout...")
print 'Performing layout ...'
if len(args) > 1:
Layout = parselayout.parseLayoutFile(args[1])
# Do the layout, updating offsets for each component job.
X = OriginX + config.Config['leftmargin']
Y = OriginY + config.Config['bottommargin']
for row in Layout:
row.setPosition(X, Y)
Y += row.height_in() + config.Config['yspacing']
# Construct a canonical placement from the layout
Place = placement.Placement()
Place.addFromLayout(Layout)
del Layout
elif config.AutoSearchType == FROM_FILE:
Place = placement.Placement()
Place.addFromFile(config.PlacementFile, config.Jobs)
else:
# Do an automatic layout based on our tiling algorithm.
tile = tile_jobs(config.Jobs.values())
Place = placement.Placement()
Place.addFromTiling(tile, OriginX + config.Config['leftmargin'], OriginY + config.Config['bottommargin'])
(MaxXExtent,MaxYExtent) = Place.extents()
MaxXExtent += config.Config['rightmargin']
MaxYExtent += config.Config['topmargin']
# Start printing out the Gerbers. In preparation for drawing cut marks
# and crop marks, make sure we have an aperture to draw with. Use a 10mil line.
# If we're doing a fabrication drawing, we'll need a 1mil line.
OutputFiles = []
try:
fullname = config.MergeOutputFiles['placement']
except KeyError:
fullname = 'merged.placement.txt'
Place.write(fullname)
OutputFiles.append(fullname)
# For cut lines
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cutlinewidth'])
drawing_code_cut = aptable.findInApertureTable(AP)
if drawing_code_cut is None:
drawing_code_cut = aptable.addToApertureTable(AP)
# For crop marks
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cropmarkwidth'])
drawing_code_crop = aptable.findInApertureTable(AP)
if drawing_code_crop is None:
drawing_code_crop = aptable.addToApertureTable(AP)
# For fiducials
drawing_code_fiducial_copper = drawing_code_fiducial_soldermask = None
if config.Config['fiducialpoints']:
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialcopperdiameter'])
drawing_code_fiducial_copper = aptable.findInApertureTable(AP)
if drawing_code_fiducial_copper is None:
drawing_code_fiducial_copper = aptable.addToApertureTable(AP)
AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialmaskdiameter'])
drawing_code_fiducial_soldermask = aptable.findInApertureTable(AP)
if drawing_code_fiducial_soldermask is None:
drawing_code_fiducial_soldermask = aptable.addToApertureTable(AP)
# For fabrication drawing.
AP = aptable.Aperture(aptable.Circle, 'D??', 0.001)
drawing_code1 = aptable.findInApertureTable(AP)
if drawing_code1 is None:
drawing_code1 = aptable.addToApertureTable(AP)
updateGUI("Writing merged files...")
print 'Writing merged output files ...'
for layername in config.LayerList.keys():
lname = layername
if lname[0]=='*':
lname = lname[1:]
try:
fullname = config.MergeOutputFiles[layername]
except KeyError:
fullname = 'merged.%s.ger' % lname
OutputFiles.append(fullname)
#print 'Writing %s ...' % fullname
fid = file(fullname, 'wt')
writeGerberHeader(fid)
# Determine which apertures and macros are truly needed
apUsedDict = {}
apmUsedDict = {}
for job in Place.jobs:
apd, apmd = job.aperturesAndMacros(layername)
apUsedDict.update(apd)
apmUsedDict.update(apmd)
# Increase aperature sizes to match minimum feature dimension
if config.MinimumFeatureDimension.has_key(layername):
print ' Thickening', lname, 'feature dimensions ...'
# Fix each aperture used in this layer
for ap in apUsedDict.keys():
new = config.GAT[ap].getAdjusted( config.MinimumFeatureDimension[layername] )
if not new: ## current aperture size met minimum requirement
continue
else: ## new aperture was created
new_code = aptable.findOrAddAperture(new) ## get name of existing aperture or create new one if needed
del apUsedDict[ap] ## the old aperture is no longer used in this layer
apUsedDict[new_code] = None ## the new aperture will be used in this layer
# Replace all references to the old aperture with the new one
for joblayout in Place.jobs:
job = joblayout.job ##access job inside job layout
temp = []
if job.hasLayer(layername):
for x in job.commands[layername]:
if x == ap:
temp.append(new_code) ## replace old aperture with new one
else:
temp.append(x) ## keep old command
job.commands[layername] = temp
if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']):
apUsedDict[drawing_code_cut]=None
if config.Config['cropmarklayers'] and (layername in config.Config['cropmarklayers']):
apUsedDict[drawing_code_crop]=None
if config.Config['fiducialpoints']:
if ((layername=='*toplayer') or (layername=='*bottomlayer')):
apUsedDict[drawing_code_fiducial_copper] = None
elif ((layername=='*topsoldermask') or (layername=='*bottomsoldermask')):
apUsedDict[drawing_code_fiducial_soldermask] = None
# Write only necessary macro and aperture definitions to Gerber file
writeApertureMacros(fid, apmUsedDict)
writeApertures(fid, apUsedDict)
#for row in Layout:
# row.writeGerber(fid, layername)
# # Do cut lines
# if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']):
# fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture
# row.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent)
# Finally, write actual flash data
for job in Place.jobs:
updateGUI("Writing merged output files...")
job.writeGerber(fid, layername)
if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']):
fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture
job.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent)
if config.Config['cropmarklayers']:
if layername in config.Config['cropmarklayers']:
writeCropMarks(fid, drawing_code_crop, OriginX, OriginY, MaxXExtent, MaxYExtent)
if config.Config['fiducialpoints']:
if ((layername=='*toplayer') or (layername=='*bottomlayer')):
writeFiducials(fid, drawing_code_fiducial_copper, OriginX, OriginY, MaxXExtent, MaxYExtent)
elif ((layername=='*topsoldermask') or (layername=='*bottomsoldermask')):
writeFiducials(fid, drawing_code_fiducial_soldermask, OriginX, OriginY, MaxXExtent, MaxYExtent)
writeGerberFooter(fid)
fid.close()
# Write board outline layer if selected
fullname = config.Config['outlinelayerfile']
if fullname and fullname.lower() != "none":
OutputFiles.append(fullname)
#print 'Writing %s ...' % fullname
fid = file(fullname, 'wt')
writeGerberHeader(fid)
# Write width-1 aperture to file
AP = aptable.Aperture(aptable.Circle, 'D10', 0.001)
AP.writeDef(fid)
# Choose drawing aperture D10
fid.write('D10*\n')
# Draw the rectangle
fid.write('X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right
fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left
writeGerberFooter(fid)
fid.close()
# Write scoring layer if selected
fullname = config.Config['scoringfile']
if fullname and fullname.lower() != "none":
OutputFiles.append(fullname)
#print 'Writing %s ...' % fullname
fid = file(fullname, 'wt')
writeGerberHeader(fid)
# Write width-1 aperture to file
AP = aptable.Aperture(aptable.Circle, 'D10', 0.001)
AP.writeDef(fid)
# Choose drawing aperture D10
fid.write('D10*\n')
# Draw the scoring lines
scoring.writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent)
writeGerberFooter(fid)
fid.close()
# Get a list of all tools used by merging keys from each job's dictionary
# of tools.
if 0:
Tools = {}
for job in config.Jobs.values():
for key in job.xcommands.keys():
Tools[key] = 1
Tools = Tools.keys()
Tools.sort()
else:
toolNum = 0
# First construct global mapping of diameters to tool numbers
for job in config.Jobs.values():
for tool,diam in job.xdiam.items():
if config.GlobalToolRMap.has_key(diam):
continue
toolNum += 1
config.GlobalToolRMap[diam] = "T%02d" % toolNum
# Cluster similar tool sizes to reduce number of drills
if config.Config['drillclustertolerance'] > 0:
config.GlobalToolRMap = drillcluster.cluster( config.GlobalToolRMap, config.Config['drillclustertolerance'] )
drillcluster.remap( Place.jobs, config.GlobalToolRMap.items() )
# Now construct mapping of tool numbers to diameters
for diam,tool in config.GlobalToolRMap.items():
config.GlobalToolMap[tool] = diam
# Tools is just a list of tool names
Tools = config.GlobalToolMap.keys()
Tools.sort()
fullname = config.Config['fabricationdrawingfile']
if fullname and fullname.lower() != 'none':
if len(Tools) > strokes.MaxNumDrillTools:
raise RuntimeError, "Only %d different tool sizes supported for fabrication drawing." % strokes.MaxNumDrillTools
OutputFiles.append(fullname)
#print 'Writing %s ...' % fullname
fid = file(fullname, 'wt')
writeGerberHeader(fid)
writeApertures(fid, {drawing_code1: None})
fid.write('%s*\n' % drawing_code1) # Choose drawing aperture
fabdrawing.writeFabDrawing(fid, Place, Tools, OriginX, OriginY, MaxXExtent, MaxYExtent)
writeGerberFooter(fid)
fid.close()
# Finally, print out the Excellon
try:
fullname = config.MergeOutputFiles['drills']
except KeyError:
fullname = 'merged.drills.xln'
OutputFiles.append(fullname)
#print 'Writing %s ...' % fullname
fid = file(fullname, 'wt')
writeExcellonHeader(fid)
# Ensure each one of our tools is represented in the tool list specified
# by the user.
for tool in Tools:
try:
size = config.GlobalToolMap[tool]
except:
raise RuntimeError, "INTERNAL ERROR: Tool code %s not found in global tool map" % tool
writeExcellonTool(fid, tool, size)
#for row in Layout:
# row.writeExcellon(fid, size)
for job in Place.jobs:
job.writeExcellon(fid, size)
writeExcellonFooter(fid)
fid.close()
updateGUI("Closing files...")
# Compute stats
jobarea = 0.0
#for row in Layout:
# jobarea += row.jobarea()
for job in Place.jobs:
jobarea += job.jobarea()
totalarea = ((MaxXExtent-OriginX)*(MaxYExtent-OriginY))
ToolStats = {}
drillhits = 0
for tool in Tools:
ToolStats[tool]=0
#for row in Layout:
# hits = row.drillhits(config.GlobalToolMap[tool])
# ToolStats[tool] += hits
# drillhits += hits
for job in Place.jobs:
hits = job.drillhits(config.GlobalToolMap[tool])
ToolStats[tool] += hits
drillhits += hits
try:
fullname = config.MergeOutputFiles['toollist']
except KeyError:
fullname = 'merged.toollist.drl'
OutputFiles.append(fullname)
#print 'Writing %s ...' % fullname
fid = file(fullname, 'wt')
print '-'*50
print ' Job Size : %f" x %f"' % (MaxXExtent-OriginX, MaxYExtent-OriginY)
print ' Job Area : %.2f sq. in.' % totalarea
print ' Area Usage : %.1f%%' % (jobarea/totalarea*100)
print ' Drill hits : %d' % drillhits
print 'Drill density : %.1f hits/sq.in.' % (drillhits/totalarea)
print '\nTool List:'
smallestDrill = 999.9
for tool in Tools:
if ToolStats[tool]:
fid.write('%s %.4fin\n' % (tool, config.GlobalToolMap[tool]))
print ' %s %.4f" %5d hits' % (tool, config.GlobalToolMap[tool], ToolStats[tool])
smallestDrill = min(smallestDrill, config.GlobalToolMap[tool])
fid.close()
print "Smallest Tool: %.4fin" % smallestDrill
print
print 'Output Files :'
for f in OutputFiles:
print ' ', f
if (MaxXExtent-OriginX)>config.Config['panelwidth'] or (MaxYExtent-OriginY)>config.Config['panelheight']:
print '*'*75
print '*'
print '* ERROR: Merged job exceeds panel dimensions of %.1f"x%.1f"' % (config.Config['panelwidth'],config.Config['panelheight'])
print '*'
print '*'*75
sys.exit(1)
# Done!
return 0
def updateGUI(text = None):
global GUI
if GUI != None:
GUI.updateProgress(text)
if __name__=="__main__":
try:
opts, args = getopt.getopt(sys.argv[1:], 'hv', ['help', 'version', 'octagons=', 'random-search', 'full-search', 'rs-fsjobs=', 'search-timeout=', 'place-file=', 'no-trim-gerber', 'no-trim-excellon'])
except getopt.GetoptError:
usage()
for opt, arg in opts:
if opt in ('-h', '--help'):
usage()
elif opt in ('-v', '--version'):
print """
GerbMerge Version %d.%d -- Combine multiple Gerber/Excellon files
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of this license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
""" % (VERSION_MAJOR, VERSION_MINOR)
sys.exit(0)
elif opt in ('--octagons', '--random-search','--full-search','--rs-fsjobs','--place-file','--no-trim-gerber','--no-trim-excellon', '--search-timeout'):
pass ## arguments are valid
else:
raise RuntimeError, "Unknown option: %s" % opt
if len(args) > 2 or len(args) < 1:
usage()
disclaimer()
sys.exit(merge(opts, args)) ## run germberge
# vim: expandtab ts=2 sw=2 ai syntax=python

1288
gerbmerge/jobs.py Normal file

File diff suppressed because it is too large Load Diff

167
gerbmerge/makestroke.py Normal file
View File

@ -0,0 +1,167 @@
#!/usr/bin/env python
"""Support for writing characters and graphics to Gerber files
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import math
import strokes
# Define percentage of cell height and width to determine
# intercharacter spacing
SpacingX = 1.20
SpacingY = 1.20
# Arrow dimensions
BarLength = 1500 # Length of dimension line
ArrowWidth = 750 # How broad the arrow is
ArrowLength = 750 # How far back from dimension line it is
ArrowStemLength = 1250 # How long the arrow stem extends from center point
#################################################################
# Arrow directions
FacingLeft=0 # 0 degrees
FacingDown=1 # 90 degrees counterclockwise
FacingRight=2 # 180 degrees
FacingUp=3 # 270 degrees
SpacingDX = 10*int(round(strokes.MaxWidth*SpacingX))
SpacingDY = 10*int(round(strokes.MaxHeight*SpacingY))
RotatedGlyphs={}
# Default arrow glyph is at 0 degrees rotation, facing left
ArrowGlyph = [ [(0,-BarLength/2), (0, BarLength/2)],
[(ArrowLength,ArrowWidth/2), (0,0), (ArrowLength,-ArrowWidth/2)],
[(0,0), (ArrowStemLength,0)]
]
def rotateGlyph(glyph, degrees, glyphName):
"""Rotate a glyph counterclockwise by given number of degrees. The glyph
is a list of lists, where each sub-list is a connected path."""
try:
return RotatedGlyphs["%.1f_%s" % (degrees, glyphName)]
except KeyError:
pass # Not cached yet
rad = degrees/180.0*math.pi
cosx = math.cos(rad)
sinx = math.sin(rad)
newglyph = []
for path in glyph:
newpath = []
for X,Y in path:
x = int(round(X*cosx - Y*sinx))
y = int(round(X*sinx + Y*cosx))
newpath.append((x,y))
newglyph.append(newpath)
RotatedGlyphs["%.1f_%s" % (degrees, glyphName)] = newglyph
return newglyph
def writeFlash(fid, X, Y, D):
fid.write("X%07dY%07dD%02d*\n" % (X,Y,D))
def drawPolyline(fid, L, offX, offY, scale=1):
for ix in range(len(L)):
X,Y = L[ix]
X *= scale
Y *= scale
if ix==0:
writeFlash(fid, X+offX, Y+offY, 2)
else:
writeFlash(fid, X+offX, Y+offY, 1)
def writeGlyph(fid, glyph, X, Y, degrees, glyphName=None):
if not glyphName:
glyphName = str(glyph)
for path in rotateGlyph(glyph, degrees, glyphName):
drawPolyline(fid, path, X, Y, 10)
def writeChar(fid, c, X, Y, degrees):
if c==' ': return
try:
glyph = strokes.StrokeMap[c]
except:
raise RuntimeError, 'No glyph for character %s' % hex(ord(c))
writeGlyph(fid, glyph, X, Y, degrees, c)
def writeString(fid, s, X, Y, degrees):
posX = X
posY = Y
rad = degrees/180.0*math.pi
dX = int(round(math.cos(rad)*SpacingDX))
dY = int(round(math.sin(rad)*SpacingDX))
if 0:
if dX < 0:
# Always print text left to right
dX = -dX
s = list(s)
s.reverse()
s = string.join(s, '')
for char in s:
writeChar(fid, char, posX, posY, degrees)
posX += dX
posY += dY
def drawLine(fid, X1, Y1, X2, Y2):
drawPolyline(fid, [(X1,Y1), (X2,Y2)], 0, 0)
def boundingBox(s, X1, Y1):
"Return (X1,Y1),(X2,Y2) for given string"
if not s:
return (X1, Y1), (X1, Y1)
X2 = X1 + (len(s)-1)*SpacingDX + 10*strokes.MaxWidth
Y2 = Y1 + 10*strokes.MaxHeight # Not including descenders
return (X1, Y1), (X2, Y2)
def drawDimensionArrow(fid, X, Y, facing):
writeGlyph(fid, ArrowGlyph, X, Y, facing*90, "Arrow")
def drawDrillHit(fid, X, Y, toolNum):
writeGlyph(fid, strokes.DrillStrokeList[toolNum], X, Y, 0, "Drill%02d" % toolNum)
if __name__=="__main__":
import string
s = string.digits+string.letters+string.punctuation
#s = "The quick brown fox jumped over the lazy dog!"
fid = file('test.ger','wt')
fid.write("""G75*
G70*
%OFA0B0*%
%FSAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
*%
%ADD10C,0.0100*%
D10*
""")
writeString(fid, s, 0, 0, 0)
drawDimensionArrow(fid, 0, 5000, FacingLeft)
drawDimensionArrow(fid, 5000, 5000, FacingRight)
drawDimensionArrow(fid, 0, 10000, FacingUp)
drawDimensionArrow(fid, 5000, 10000, FacingDown)
for diam in range(0,strokes.MaxNumDrillTools):
writeGlyph(fid, strokes.DrillStrokeList[diam], diam*1250, 15000, 0, "%02d" % diam)
fid.write("M02*\n")
fid.close()

338
gerbmerge/parselayout.py Normal file
View File

@ -0,0 +1,338 @@
#!/usr/bin/env python
"""
Parse the job layout specification file.
Requires:
- SimpleParse 2.1 or higher
http://simpleparse.sourceforge.net
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import string
from simpleparse.parser import Parser
import config
import jobs
declaration = r'''
file := (commentline/nullline/rowspec)+
rowspec := ts, 'Row', ws, '{'!, ts, comment?, '\n', rowjob+, ts, '}'!, ts, comment?, '\n'
rowjob := jobspec/colspec/commentline/nullline
colspec := ts, 'Col', ws, '{'!, ts, comment?, '\n', coljob+, ts, '}'!, ts, comment?, '\n'
coljob := jobspec/rowspec/commentline/nullline
jobspec := ts, (paneljobspec/basicjobspec), ts, comment?, '\n'
basicjobspec := id, (rotation)?
paneljobspec := 'Not yet implemented'
#paneljobspec := int, [xX], int, ws, basicjobspec
comment := ([#;]/'//'), -'\n'*
commentline := ts, comment, '\n'
nullline := ts, '\n'
rotation := ws, 'Rotate', ('90'/'180'/'270')?
ws := [ \t]+
ts := [ \t]*
id := [a-zA-Z0-9], [a-zA-Z0-9_-]*
int := [0-9]+
'''
class Panel: # Meant to be subclassed as either a Row() or Col()
def __init__(self):
self.x = None
self.y = None
self.jobs = [] # List (left-to-right or bottom-to-top) of JobLayout() or Row()/Col() objects
def canonicalize(self): # Return plain list of JobLayout objects at the roots of all trees
L = []
for job in self.jobs:
L = L + job.canonicalize()
return L
def addjob(self, job): # Either a JobLayout class or Panel (sub)class
assert isinstance(job, Panel) or isinstance(job, jobs.JobLayout)
self.jobs.append(job)
def addwidths(self):
"Return width in inches"
width = 0.0
for job in self.jobs:
width += job.width_in() + config.Config['xspacing']
width -= config.Config['xspacing']
return width
def maxwidths(self):
"Return maximum width in inches of any one subpanel"
width = 0.0
for job in self.jobs:
width = max(width,job.width_in())
return width
def addheights(self):
"Return height in inches"
height = 0.0
for job in self.jobs:
height += job.height_in() + config.Config['yspacing']
height -= config.Config['yspacing']
return height
def maxheights(self):
"Return maximum height in inches of any one subpanel"
height = 0.0
for job in self.jobs:
height = max(height,job.height_in())
return height
def writeGerber(self, fid, layername):
for job in self.jobs:
job.writeGerber(fid, layername)
def writeExcellon(self, fid, tool):
for job in self.jobs:
job.writeExcellon(fid, tool)
def writeDrillHits(self, fid, tool, toolNum):
for job in self.jobs:
job.writeDrillHits(fid, tool, toolNum)
def writeCutLines(self, fid, drawing_code, X1, Y1, X2, Y2):
for job in self.jobs:
job.writeCutLines(fid, drawing_code, X1, Y1, X2, Y2)
def drillhits(self, tool):
hits = 0
for job in self.jobs:
hits += job.drillhits(tool)
return hits
def jobarea(self):
area = 0.0
for job in self.jobs:
area += job.jobarea()
return area
class Row(Panel):
def __init__(self):
Panel.__init__(self)
self.LR = 1 # Horizontal arrangement
def width_in(self):
return self.addwidths()
def height_in(self):
return self.maxheights()
def setPosition(self, x, y): # In inches
self.x = x
self.y = y
for job in self.jobs:
job.setPosition(x,y)
x += job.width_in() + config.Config['xspacing']
class Col(Panel):
def __init__(self):
Panel.__init__(self)
self.LR = 0 # Vertical arrangement
def width_in(self):
return self.maxwidths()
def height_in(self):
return self.addheights()
def setPosition(self, x, y): # In inches
self.x = x
self.y = y
for job in self.jobs:
job.setPosition(x,y)
y += job.height_in() + config.Config['yspacing']
def canonicalizePanel(panel):
L = []
for job in panel:
L = L + job.canonicalize()
return L
def findJob(jobname, rotated, Jobs=config.Jobs):
"""
Find a job in config.Jobs, possibly rotating it
If job not in config.Jobs add it for future reference
Return found job
"""
if rotated == 90:
fullname = jobname + '*rotated90'
elif rotated == 180:
fullname = jobname + '*rotated180'
elif rotated == 270:
fullname = jobname + '*rotated270'
else:
fullname = jobname
try:
for existingjob in Jobs.keys():
if existingjob.lower() == fullname.lower(): ## job names are case insensitive
job = Jobs[existingjob]
return jobs.JobLayout(job)
except:
pass
# Perhaps we just don't have a rotated job yet
if rotated:
try:
for existingjob in Jobs.keys():
if existingjob.lower() == jobname.lower(): ## job names are case insensitive
job = Jobs[existingjob]
except:
raise RuntimeError, "Job name '%s' not found" % jobname
else:
raise RuntimeError, "Job name '%s' not found" % jobname
# Make a rotated job
job = jobs.rotateJob(job, rotated)
Jobs[fullname] = job
return jobs.JobLayout(job)
def parseJobSpec(spec, data):
for jobspec in spec:
if jobspec[0] in ('ts','comment'): continue
assert jobspec[0] in ('paneljobspec','basicjobspec')
if jobspec[0] == 'basicjobspec':
namefield = jobspec[3][0]
jobname = data[namefield[1]:namefield[2]]
if len(jobspec[3]) > 1:
rotationfield = jobspec[3][1]
rotation = data[ rotationfield[1] + 1: rotationfield[2] ]
if (rotation == "Rotate") or (rotation == "Rotate90"):
rotated = 90
elif rotation == "Rotate180":
rotated = 180
elif rotation == "Rotate270":
rotated = 270
else:
raise RuntimeError, "Unsupported rotation: %s" % rotation
else:
rotated = 0
return findJob(jobname, rotated)
else:
raise RuntimeError, "Matrix panels not yet supported"
def parseColSpec(spec, data):
jobs = Col()
for coljob in spec:
if coljob[0] in ('ts','ws','comment'): continue
assert coljob[0] == 'coljob'
job = coljob[3][0]
if job[0] in ('commentline','nullline'): continue
assert job[0] in ('jobspec','rowspec')
if job[0] == 'jobspec':
jobs.addjob(parseJobSpec(job[3],data))
else:
jobs.addjob(parseRowSpec(job[3],data))
return jobs
def parseRowSpec(spec, data):
jobs = Row()
for rowjob in spec:
if rowjob[0] in ('ts','ws','comment'): continue
assert rowjob[0] == 'rowjob'
job = rowjob[3][0]
if job[0] in ('commentline','nullline'): continue
assert job[0] in ('jobspec','colspec')
if job[0] == 'jobspec':
jobs.addjob(parseJobSpec(job[3],data))
else:
jobs.addjob(parseColSpec(job[3],data))
return jobs
def parseLayoutFile(fname):
"""config.Jobs is a dictionary of ('jobname', Job Object).
The return value is a nested array. The primary dimension
of the array is one row:
[ Row1, Row2, Row3 ]
Each row element consists of a list of jobs or columns (i.e.,
JobLayout or Col objects).
Each column consists of a list of either jobs or rows.
These are recursive, so it can look like:
[
Row([JobLayout(), Col([ Row([JobLayout(), JobLayout()]),
JobLayout() ]), JobLayout() ]), # That was row 0
Row([JobLayout(), JobLayout()]) # That was row 1
]
This is a panel with two rows. In the first row there is
a job, a column, and another job, from left to right. In the
second row there are two jobs, from left to right.
The column in the first row has two jobs side by side, then
another one above them.
"""
try:
fid = file(fname, 'rt')
except Exception, detail:
raise RuntimeError, "Unable to open layout file: %s\n %s" % (fname, str(detail))
data = fid.read()
fid.close()
parser = Parser(declaration, "file")
# Replace all CR's in data with nothing, to convert DOS line endings
# to unix format (all LF's).
data = string.replace(data, '\x0D', '')
tree = parser.parse(data)
# Last element of tree is number of characters parsed
if not tree[0]:
raise RuntimeError, "Layout file cannot be parsed"
if tree[2] != len(data):
raise RuntimeError, "Parse error at character %d in layout file" % tree[2]
Rows = []
for rowspec in tree[1]:
if rowspec[0] in ('nullline', 'commentline'): continue
assert rowspec[0]=='rowspec'
Rows.append(parseRowSpec(rowspec[3], data))
return Rows
if __name__=="__main__":
fid = file(sys.argv[1])
testdata = fid.read()
fid.close()
parser = Parser(declaration, "file")
import pprint
pprint.pprint(parser.parse(testdata))

108
gerbmerge/placement.py Normal file
View File

@ -0,0 +1,108 @@
#!/usr/bin/env python
"""A placement is a final arrangement of jobs at given (X,Y) positions.
This class is intended to "un-pack" an arragement of jobs constructed
manually through Layout/Panel/JobLayout/etc. (i.e., a layout.def file)
or automatically through a Tiling. From either source, the result is
simply a list of jobs.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import re
import parselayout
import jobs
class Placement:
def __init__(self):
self.jobs = [] # A list of JobLayout objects
def addFromLayout(self, Layout):
# Layout is a recursive list of JobLayout items. At the end
# of each tree there is a JobLayout object which has a 'job'
# member, which is what we're looking for. Fortunately, the
# canonicalize() function flattens the tree.
#
# Positions of jobs have already been set (we're assuming)
# prior to calling this function.
self.jobs = self.jobs + parselayout.canonicalizePanel(Layout)
def addFromTiling(self, T, OriginX, OriginY):
# T is a Tiling. Calling its canonicalize() method will construct
# a list of JobLayout objects and set the (X,Y) position of each
# object.
self.jobs = self.jobs + T.canonicalize(OriginX,OriginY)
def extents(self):
"""Return the maximum X and Y value over all jobs"""
maxX = 0.0
maxY = 0.0
for job in self.jobs:
maxX = max(maxX, job.x+job.width_in())
maxY = max(maxY, job.y+job.height_in())
return (maxX,maxY)
def write(self, fname):
"""Write placement to a file"""
fid = file(fname, 'wt')
for job in self.jobs:
fid.write('%s %.3f %.3f\n' % (job.job.name, job.x, job.y))
fid.close()
def addFromFile(self, fname, Jobs):
"""Read placement from a file, placed against jobs in Jobs list"""
pat = re.compile(r'\s*(\S+)\s+(\S+)\s+(\S+)')
comment = re.compile(r'\s*(?:#.+)?$')
try:
fid = file(fname, 'rt')
except:
print 'Unable to open placement file: "%s"' % fname
sys.exit(1)
lines = fid.readlines()
fid.close()
for line in lines:
if comment.match(line): continue
match = pat.match(line)
if not match:
print 'Cannot interpret placement line in placement file:\n %s' % line
sys.exit(1)
jobname, X, Y = match.groups()
try:
X = float(X)
Y = float(Y)
except:
print 'Illegal (X,Y) co-ordinates in placement file:\n %s' % line
sys.exit(1)
rotated = 0
if len(jobname) > 8:
if jobname[-8:] == '*rotated':
rotated = 90
jobname = jobname[:-8]
elif jobname[-10:] == '*rotated90':
rotated = 90
jobname = jobname[:-10]
elif jobname[-11:] == '*rotated180':
rotated = 180
jobname = jobname[:-11]
elif jobname[-11:] == '*rotated270':
rotated = 270
jobname = jobname[:-11]
addjob = parselayout.findJob(jobname, rotated, Jobs)
addjob.setPosition(X,Y)
self.jobs.append(addjob)

38
gerbmerge/schwartz.py Normal file
View File

@ -0,0 +1,38 @@
"""
Implement the Schwartizan Transform method of sorting
a list by an arbitrary metric (see the Python FAQ section
4.51).
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
def stripit(pair):
return pair[1]
def schwartz(List, Metric):
def pairing(element, M = Metric):
return (M(element), element)
paired = map(pairing, List)
paired.sort()
return map(stripit, paired)
def stripit2(pair):
return pair[0]
def schwartz2(List, Metric):
"Returns sorted list and also corresponding metrics"
def pairing(element, M = Metric):
return (M(element), element)
paired = map(pairing, List)
paired.sort()
theList = map(stripit, paired)
theMetrics = map(stripit2, paired)
return (theList, theMetrics)

301
gerbmerge/scoring.py Normal file
View File

@ -0,0 +1,301 @@
#!/usr/bin/env python
"""This file handles the writing of the scoring lines Gerber file
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import config
import util
import makestroke
# Add a horizontal line if its within the extents of the panel. Also, trim
# start and/or end points to the extents.
def addHorizontalLine(Lines, x1, x2, y, extents):
assert (x1 < x2)
# For a horizontal line, y must be above extents[1] and below extents[3].
if extents[1] < y < extents[3]:
# Now trim endpoints to be greater than extents[0] and below extents[2]
line = (max(extents[0], x1), y, min(extents[2], x2), y)
Lines.append(line)
# Add a vertical line if its within the extents of the panel. Also, trim
# start and/or end points to the extents.
def addVerticalLine(Lines, x, y1, y2, extents):
assert (y1 < y2)
# For a vertical line, x must be above extents[0] and below extents[2].
if extents[0] < x < extents[2]:
# Now trim endpoints to be greater than extents[1] and below extents[3]
line = (x, max(extents[1], y1), x, min(extents[3], y2))
Lines.append(line)
def isHorizontal(line):
return line[1]==line[3]
def isVertical(line):
return line[0]==line[2]
def clusterOrdinates(values):
"""Create a list of tuples where each tuple is a variable-length list of items
from 'values' that are all within 2 mils of each other."""
# First, make sure the values are sorted. Then, take the first one and go along
# the list clustering as many as possible.
values.sort()
currCluster = None
L = []
for val in values:
if currCluster is None:
currCluster = (val,)
else:
if (val - currCluster[0]) <= 0.002:
currCluster = currCluster + (val,)
else:
L.append(currCluster)
currCluster = (val,)
if currCluster is not None:
L.append(currCluster)
return L
def mergeHLines(Lines):
"""Lines is a list of 4-tuples (lines) that have nearly the same Y ordinate and are to be
optimized by combining overlapping lines."""
# First, make sure lines are sorted by starting X ordinate and that all lines
# proceed to the right.
Lines.sort()
for line in Lines:
assert line[0] < line[2]
# Obtain the average value of the Y ordinate and use that as the Y ordinate for
# all lines.
yavg = 0.0
for line in Lines:
yavg += line[1]
yavg /= len(Lines)
NewLines = []
# Now proceed to pick off one line at a time and try to merge it with
# the next one in sequence.
currLine = None
for line in Lines:
if currLine is None:
currLine = line
else:
# If the line to examine starts to the left of (within 0.002") the end
# of the current line, extend the current line.
if line[0] <= currLine[2]+0.002:
currLine = (currLine[0], yavg, max(line[2],currLine[2]), yavg)
else:
NewLines.append(currLine)
currLine = line
NewLines.append(currLine)
return NewLines
def sortByY(A,B):
"Helper function to sort two lines (4-tuples) by their starting Y ordinate"
return cmp(A[1], B[1])
def mergeVLines(Lines):
"""Lines is a list of 4-tuples (lines) that have nearly the same X ordinate and are to be
optimized by combining overlapping lines."""
# First, make sure lines are sorted by starting Y ordinate and that all lines
# proceed up.
Lines.sort(sortByY)
for line in Lines:
assert line[1] < line[3]
# Obtain the average value of the X ordinate and use that as the X ordinate for
# all lines.
xavg = 0.0
for line in Lines:
xavg += line[0]
xavg /= len(Lines)
NewLines = []
# Now proceed to pick off one line at a time and try to merge it with
# the next one in sequence.
currLine = None
for line in Lines:
if currLine is None:
currLine = line
else:
# If the line to examine starts below (within 0.002") the end
# of the current line, extend the current line.
if line[1] <= currLine[3]+0.002:
currLine = (xavg, currLine[1], xavg, max(line[3],currLine[3]))
else:
NewLines.append(currLine)
currLine = line
NewLines.append(currLine)
return NewLines
def mergeLines(Lines):
# All lines extend up (vertical) and to the right (horizontal). First, do
# simple merges. Sort all lines, which will order the lines with starting
# points in increasing X order (i.e., to the right).
Lines.sort()
# Now sort the lines into horizontal lines and vertical lines. For each
# ordinate, group all lines by that ordinate in a dictionary. Thus, all
# horizontal lines will be grouped together by Y ordinate, and all
# vertical lines will be grouped together by X ordinate.
HLines = {}
VLines = {}
for line in Lines:
if isHorizontal(line):
try:
HLines[line[1]].append(line)
except KeyError:
HLines[line[1]] = [line]
else:
try:
VLines[line[0]].append(line)
except KeyError:
VLines[line[0]] = [line]
# I don't think the next two blocks of code are necessary (merging lines
# that are at exactly the same ordinate) since the last two blocks of
# code do the same thing more generically by merging lines at close-enough
# ordinates.
# Extend horizontal lines
NewHLines = {}
for yval,lines in HLines.items():
# yval is the Y ordinate of this group of lines. lines is the set of all
# lines with this Y ordinate.
NewHLines[yval] = []
# Try to extend the first element of this list, which will be the leftmost.
xline = lines[0]
for line in lines[1:]:
# If this line's left edge is within 2 mil of the right edge of the line
# we're currently trying to grow, then grow it.
if abs(line[0] - xline[2]) <= 0.002: # Arbitrary 2mil?
# Extend...
xline = (xline[0], xline[1], line[2], xline[1])
else:
# ...otherwise, append the currently-extended line and make this
# line the new one we try to extend.
NewHLines[yval].append(xline)
xline = line
NewHLines[yval].append(xline)
# Extend vertical lines
NewVLines = {}
for xval,lines in VLines.items():
# xval is the X ordinate of this group of lines. lines is the set of all
# lines with this X ordinate.
NewVLines[xval] = []
# Try to extend the first element of this list, which will be the bottom-most.
xline = lines[0]
for line in lines[1:]:
# If this line's bottom edge is within 2 mil of the top edge of the line
# we're currently trying to grow, then grow it.
if abs(line[1] - xline[3]) <= 0.002: # Arbitrary 2mil?
# Extend...
xline = (xline[0], xline[1], xline[0], line[3])
else:
# ...otherwise, append the currently-extended line and make this
# line the new one we try to extend.
NewVLines[xval].append(xline)
xline = line
NewVLines[xval].append(xline)
HLines = NewHLines
VLines = NewVLines
NewHLines = []
NewVLines = []
# Now combine lines that have their endpoints either very near each other
# or within each other. We will have to sort all horizontal lines by their
# Y ordinates and group them according to Y ordinates that are close enough
# to each other.
yvals = HLines.keys()
clusters = clusterOrdinates(yvals) # A list of clustered tuples containing yvals
for cluster in clusters:
clusterLines = []
for yval in cluster:
clusterLines.extend(HLines[yval])
# clusterLines is now a list of lines (4-tuples) that all have nearly the same
# Y ordinate. Merge them together.
NewHLines.extend(mergeHLines(clusterLines))
xvals = VLines.keys()
clusters = clusterOrdinates(xvals)
for cluster in clusters:
clusterLines = []
for xval in cluster:
clusterLines.extend(VLines[xval])
# clusterLines is now a list of lines (4-tuples) that all have nearly the same
# X ordinate. Merge them together.
NewVLines.extend(mergeVLines(clusterLines))
Lines = NewHLines + NewVLines
return Lines
# Main entry point. Gerber file has already been opened, header written
# out, 1mil tool selected.
def writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent):
# For each job, write out 4 score lines, above, to the right, below, and
# to the left. After we collect all potential scoring lines, we worry
# about merging, etc.
dx = config.Config['xspacing']/2.0
dy = config.Config['yspacing']/2.0
extents = (OriginX, OriginY, MaxXExtent, MaxYExtent)
Lines = []
for layout in Place.jobs:
x = layout.x - dx
y = layout.y - dy
X = layout.x + layout.width_in() + dx
Y = layout.y + layout.height_in() + dy
# Just so we don't get 3.75000000004 and 3.75000000009, we round to
# 2.5 limits.
x,y,X,Y = [round(val,5) for val in [x,y,X,Y]]
if 0: # Scoring lines go all the way across the panel now
addHorizontalLine(Lines, x, X, Y, extents) # above job
addVerticalLine(Lines, X, y, Y, extents) # to the right of job
addHorizontalLine(Lines, x, X, y, extents) # below job
addVerticalLine(Lines, x, y, Y, extents) # to the left of job
else:
addHorizontalLine(Lines, OriginX, MaxXExtent, Y, extents) # above job
addVerticalLine(Lines, X, OriginY, MaxYExtent, extents) # to the right of job
addHorizontalLine(Lines, OriginX, MaxXExtent, y, extents) # below job
addVerticalLine(Lines, x, OriginY, MaxYExtent, extents) # to the left of job
# Combine disparate lines into single lines
Lines = mergeLines(Lines)
#for line in Lines:
# print [round(x,3) for x in line]
# Write 'em out
for line in Lines:
makestroke.drawPolyline(fid, [(util.in2gerb(line[0]),util.in2gerb(line[1])), \
(util.in2gerb(line[2]),util.in2gerb(line[3]))], 0, 0)
# vim: expandtab ts=2 sw=2 ai syntax=python

177
gerbmerge/specs.py Normal file
View File

@ -0,0 +1,177 @@
#!/usr/bin/env python
"""
Regular expression, SimpleParse, ane message constants.
Requires:
- SimpleParse 2.1 or higher
http://simpleparse.sourceforge.net
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import re
from simpleparse.parser import Parser
DISCLAIMER = """
****************************************************
* R E A D C A R E F U L L Y *
* *
* This program comes with no warranty. You use *
* this program at your own risk. Do not submit *
* board files for manufacture until you have *
* thoroughly inspected the output of this program *
* using a previewing program such as: *
* *
* Windows: *
* - GC-Prevue <http://www.graphicode.com> *
* - ViewMate <http://www.pentalogix.com> *
* *
* Linux: *
* - gerbv <http://gerbv.sourceforge.net> *
* *
* By using this program you agree to take full *
* responsibility for the correctness of the data *
* that is generated by this program. *
****************************************************
"""[1:-1]
# [Options] section defaults Data types: "L" = layers (will show layer selection)
# "D" = decimal
# "DP" = possitive decimal
# "I" = integer
# "IP" = integer positive
# "PI" = path input (will show open dialog)
# "PO" = path output (will show save dialog)
# "S" = string
# "B" = boolean
# "BI" = boolean as integer
#
# THESE DATA TYPES ARE FIXED - CODE MUST CHANGE IF TYPES ARE ADDED/MODIFIED
DEFAULT_OPTIONS = {
# Spacing in horizontal direction
'xspacing': ('0.125', "DP", "XSpacing", "1 XSPACING_HELP"),
# Spacing in vertical direction
'yspacing': ('0.125', "DP", "YSpacing", "2 YSPACING_HELP"),
# X-Dimension maximum panel size (Olimex)
'panelwidth': ('12.6', "DP", "PanelWidth", "3 PANEL_WIDTH"),
# Y-Dimension maximum panel size (Olimex)
'panelheight': ('7.8', "DP", "PanelHeight", "4 PanelHeight"),
# e.g., *toplayer,*bottomlayer
'cropmarklayers': (None, "L", "CropMarkLayers", "5 CropMarkLayers"),
# Width (inches) of crop lines
'cropmarkwidth': ('0.01', "DP", "CropMarkWidth", "6 CropMarkWidth"),
# as for cropmarklayers
'cutlinelayers': (None, "L", "CutLineLayers", "7 CutLineLayers"),
# Width (inches) of cut lines
'cutlinewidth': ('0.01', "DP", "CutLineWidth", "8 CutLineWidth"),
# Minimum dimension for selected layers
'minimumfeaturesize': (None, "S", "MinimumFeatureSize", "Use this option to automatically thicken features on particular layers.\nThis is intended for thickening silkscreen to some minimum width.\nThe value of this option must be a comma-separated list\nof layer names followed by minimum feature sizes (in inches) for that layer.\nComment this out to disable thickening. Example usage is:\n\nMinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008"),
# Name of file containing default tool list
'toollist': (None, "PI", "ToolList", "10 ToolList"),
# Tolerance for clustering drill sizes
'drillclustertolerance': ('.002', "DP", "DrillClusterTolerance", "11 DrillClusterTolerance"),
# Set to 1 to allow multiple jobs to have non-matching layers
'allowmissinglayers': (0, "BI", "AllowMissingLayers", "12 AllowMissingLayers"),
# Name of file to which to write fabrication drawing, or None
'fabricationdrawingfile': (None, "PO", "FabricationDrawingFile", "13 FabricationDrawingFile"),
# Name of file containing text to write to fab drawing
'fabricationdrawingtext': (None, "PI", "FabricationDrawingText", "14 FabricationDrawingText"),
# Number of digits after the decimal point in input Excellon files
'excellondecimals': (4, "IP", "ExcellonDecimals", "15 ExcellonDecimals"),
# Generate leading zeros in merged Excellon output file
'excellonleadingzeros': (0, "IP", "ExcellonLeadingZeros", "16 ExcellonLeadingZeros"),
# Name of file to which to write simple box outline, or None
'outlinelayerfile': (None, "PO", "OutlineLayerFile", "17 OutlineLayerFile"),
# Name of file to which to write scoring data, or None
'scoringfile': (None, "PO", "ScoringFile", "18 ScoringFile"),
# Inches of extra room to leave on left side of panel for tooling
'leftmargin': (0.0, "DP", "LeftMargin", "19 LeftMargin"),
# Inches of extra room to leave on top side of panel for tooling
'topmargin': (0.0, "DP", "TopMargin", "20 TopMargin"),
# Inches of extra room to leave on right side of panel for tooling
'rightmargin': (0.0, "DP", "RightMargin", "21 RightMargin"),
# Inches of extra room to leave on bottom side of panel for tooling
'bottommargin': (0.0, "DP", "BottomMargin", "22 BottomMargin"),
# List of X,Y points at which to draw fiducials
'fiducialpoints': (None, "S", "FiducialPoints", "23 FiducialPoints"),
}
DEFAULT_OPTIONS_TYPES = ["IP", "I", "DP", "D", "B", "BI", "S", "PI", "PO", "L"] # List of option types in display order
# [GerbMergeGUI] section defaults
DEFAULT_GERBMERGEGUI = {
'unit': "IN", # Unit inidicator: IN, MIL, MM
'layout': "AUTOMATIC", # Indicates layout: GRID, AUTOMATIC, MANUAL, GRID_FILE, MANUAL_FILE
'runtime': 10, # Seconds to run automatic placement
'rows': 1, # Number of rows in grid layout
'columns': 1, # Number of columns in grid layout
'mergedoutput': False, # Path of output directory
'mergedname': False, # Prefix of merged output files
'layoutfilepath': "", # Path of layout file
'placementfilepath': "", # Path of placement file
'configurationfilepath': "", # Path of configuration file
'configurationcomplete': False, # Indicates that run dialog may be skipped to upon load
}
# Job names
RE_VALID_JOB = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_-]*$')
RE_VALID_JOB_MESSAGE = "Vaild Characters: a-z, A-Z, 0-9, underscores, hyphens\nFirst Character must be: a-z, A-Z, 0-9"
RESERVED_JOB_NAMES = ("Options", "MergeOutputFiles", "GerbMergeGUI") ##not implemented yet
# Layer names
RE_VALID_LAYER = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_-]*$')
RE_VALID_LAYER_MESSAGE = "Vaild Characters: a-z, A-Z, 0-9, underscores, hyphens\nFirst Character must be: a-z, A-Z, 0-9"
DEFAULT_LAYERS = [ "BoardOutline",
"TopCopper",
"BottomCopper",
"InnerLayer2",
"InnerLayer3",
"TopSilkscreen",
"BottomSilkscreen",
"TopSoldermask",
"BottomSoldermask",
"TopSolderPasteMask",
"BottomSolderPasteMask",
"Drills" ]
REQUIRED_LAYERS = ["BoardOutline", "Drills"]
RESERVED_LAYER_NAMES = () ##add "mergeout", not implemented yet
#Output names
RE_VALID_OUTPUT_NAME = re.compile(r'^[a-zA-Z0-9_-]+$')
RE_VALID_OUTPUT_NAME_MESSAGE = "Vaild Characters: a-z, A-Z, 0-9, underscores, hyphens"
REQUIRED_LAYERS_OUTPUT = ["BoardOutline", "ToolList", "Placement", "Drills"]
# Default dictionary of layer names to file extensions
FILE_EXTENSIONS = { "boardoutline": "GBO",
"topcopper": "GTL",
"bottomcopper": "GBL",
"innerlayer2": "G2",
"innerlayer3": "G3",
"topsilkscreen": "GTO",
"bottomsilkscreen": "GBO",
"topsoldermask": "GTS",
"bottomsoldermask": "GBS",
"topsolderpastemask": "GTP",
"bottomsolderpastemask": "GBP",
"drills": "GDD",
"placement": "TXT",
"toollist": "DRL",
}
DEFAULT_EXTENSION = "GER"
#Gerbmerge options
PLACE_FILE = "--place-file="
NO_TRIM_GERBER = "--no-trim-gerber"
NO_TRIM_EXCELLON = "--no-trim-excellon"
ROTATED_OCTAGONS = "--octagons=rotate"
SEARCH_TIMEOUT = "--search-timeout="

38
gerbmerge/strokes.py Normal file

File diff suppressed because one or more lines are too long

243
gerbmerge/tilesearch1.py Normal file
View File

@ -0,0 +1,243 @@
#!/usr/bin/env python
"""Search for an optimum tiling using brute force exhaustive search
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import time
import config
import tiling
import gerbmerge
_StartTime = 0.0 # Start time of tiling
_CkpointTime = 0.0 # Next time to print stats
_Placements = 0L # Number of placements attempted
_PossiblePermutations = 0L # Number of different ways of ordering jobs
_Permutations = 0L # Number of different job orderings already computed
_TBestTiling = None # Best tiling so far
_TBestScore = float(sys.maxint) # Smallest area so far
_PrintStats = 1 # Print statistics every 3 seconds
def printTilingStats():
global _CkpointTime
_CkpointTime = time.time() + 3
if _TBestTiling:
area = _TBestTiling.area()
utilization = _TBestTiling.usedArea() / area * 100.0
else:
area = 999999.0
utilization = 0.0
percent = 100.0*_Permutations/_PossiblePermutations
print "\r %5.2f%% complete / %ld/%ld Perm/Place / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
(percent, _Permutations, _Placements, area, utilization),
if gerbmerge.GUI is not None:
sys.stdout.flush()
def bestTiling():
return _TBestTiling
def _tile_search1(Jobs, TSoFar, firstAddPoint, cfg=config.Config):
"""This recursive function does the following with an existing tiling TSoFar:
* For each 4-tuple (Xdim,Ydim,job,rjob) in Jobs, the non-rotated 'job' is selected
* For the non-rotated job, the list of valid add-points is found
* For each valid add-point, the job is placed at this point in a new,
cloned tiling.
* The function then calls its recursively with the remaining list of
jobs.
* The rotated job is then selected and the list of valid add-points is
found. Again, for each valid add-point the job is placed there in
a new, cloned tiling.
* Once again, the function calls itself recursively with the remaining
list of jobs.
* The best tiling encountered from all recursive calls is returned.
If TSoFar is None it means this combination of jobs is not tileable.
The side-effect of this function is to set _TBestTiling and _TBestScore
to the best tiling encountered so far. _TBestTiling could be None if
no valid tilings have been found so far.
"""
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PrintStats
if not TSoFar:
return (None, float(sys.maxint))
if not Jobs:
# Update the best tiling and score. If the new tiling matches
# the best score so far, compare on number of corners, trying to
# minimize them.
score = TSoFar.area()
if score < _TBestScore:
_TBestTiling,_TBestScore = TSoFar,score
elif score == _TBestScore:
if TSoFar.corners() < _TBestTiling.corners():
_TBestTiling,_TBestScore = TSoFar,score
_Placements += 1
if firstAddPoint:
_Permutations += 1
return
xspacing = cfg['xspacing']
yspacing = cfg['yspacing']
minInletSize = tiling.minDimension(Jobs)
TSoFar.removeInlets(minInletSize)
for job_ix in range(len(Jobs)):
# Pop off the next job and construct remaining_jobs, a sub-list
# of Jobs with the job we've just popped off excluded.
Xdim,Ydim,job,rjob = Jobs[job_ix]
remaining_jobs = Jobs[:job_ix]+Jobs[job_ix+1:]
if 0:
print "Level %d (%s)" % (level, job.name)
TSoFar.joblist()
for J in remaining_jobs:
print J[2].name, ", ",
print
print '-'*75
# Construct add-points for the non-rotated and rotated job.
# As an optimization, do not construct add-points for the rotated
# job if the job is a square (duh).
addpoints1 = TSoFar.validAddPoints(Xdim+xspacing,Ydim+yspacing) # unrotated job
if Xdim != Ydim:
addpoints2 = TSoFar.validAddPoints(Ydim+xspacing,Xdim+yspacing) # rotated job
else:
addpoints2 = []
# Recursively construct tilings for the non-rotated job and
# update the best-tiling-so-far as we do so.
if addpoints1:
for ix in addpoints1:
# Clone the tiling we're starting with and add the job at this
# add-point.
T = TSoFar.clone()
T.addJob(ix, Xdim+xspacing, Ydim+yspacing, job)
# Recursive call with the remaining jobs and this new tiling. The
# point behind the last parameter is simply so that _Permutations is
# only updated once for each permutation, not once per add-point.
# A permutation is some ordering of jobs (N! choices) and some
# ordering of non-rotated and rotated within that ordering (2**N
# possibilities per ordering).
_tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints1[0])
elif firstAddPoint:
# Premature prune due to not being able to put this job anywhere. We
# have pruned off 2^M permutations where M is the length of the remaining
# jobs.
_Permutations += 2L**len(remaining_jobs)
if addpoints2:
for ix in addpoints2:
# Clone the tiling we're starting with and add the job at this
# add-point. Remember that the job is rotated so swap X and Y
# dimensions.
T = TSoFar.clone()
T.addJob(ix, Ydim+xspacing, Xdim+yspacing, rjob)
# Recursive call with the remaining jobs and this new tiling.
_tile_search1(remaining_jobs, T, firstAddPoint and ix==addpoints2[0])
elif firstAddPoint:
# Premature prune due to not being able to put this job anywhere. We
# have pruned off 2^M permutations where M is the length of the remaining
# jobs.
_Permutations += 2L**len(remaining_jobs)
# If we've been at this for 3 seconds, print some status information
if _PrintStats and time.time() > _CkpointTime:
printTilingStats()
# Check for timeout
if (config.SearchTimeout > 0) and (time.time() - _StartTime > config.SearchTimeout):
raise KeyboardInterrupt
gerbmerge.updateGUI("Performing automatic layout...")
# end for each job in job list
def factorial(N):
if (N <= 1): return 1L
prod = long(N)
while (N > 2):
N -= 1
prod *= N
return prod
def initialize(printStats=1):
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PossiblePermutations, _PrintStats
_PrintStats = printStats
_Placements = 0L
_Permutations = 0L
_TBestTiling = None
_TBestScore = float(sys.maxint)
def tile_search1(Jobs, X, Y):
"""Wrapper around _tile_search1 to handle keyboard interrupt, etc."""
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore, _Permutations, _PossiblePermutations
initialize()
_StartTime = time.time()
_CkpointTime = _StartTime + 3
# There are (2**N)*(N!) possible permutations where N is the number of jobs.
# This is assuming all jobs are unique and each job has a rotation (i.e., is not
# square). Practically, these assumptions make no difference because the software
# currently doesn't optimize for cases of repeated jobs.
_PossiblePermutations = (2L**len(Jobs))*factorial(len(Jobs))
#print "Possible permutations:", _PossiblePermutations
print '='*70
print "Starting placement using exhaustive search."
print "There are %ld possible permutations..." % _PossiblePermutations,
if _PossiblePermutations < 1e4:
print "this'll take no time at all."
elif _PossiblePermutations < 1e5:
print "surf the web for a few minutes."
elif _PossiblePermutations < 1e6:
print "take a long lunch."
elif _PossiblePermutations < 1e7:
print "come back tomorrow."
else:
print "don't hold your breath."
print "Press Ctrl-C to stop and use the best placement so far."
print "Estimated maximum possible utilization is %.1f%%." % (tiling.maxUtilization(Jobs)*100)
try:
_tile_search1(Jobs, tiling.Tiling(X,Y), 1)
printTilingStats()
print
except KeyboardInterrupt:
printTilingStats()
print
print "Interrupted."
computeTime = time.time() - _StartTime
print "Computed %ld placements in %d seconds / %.1f placements/second" % (_Placements, computeTime, _Placements/computeTime)
print '='*70
return _TBestTiling

147
gerbmerge/tilesearch2.py Normal file
View File

@ -0,0 +1,147 @@
#!/usr/bin/env python
"""Tile search using random placement and evaluation. Works surprisingly well.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import time
import random
import config
import tiling
import tilesearch1
import gerbmerge
_StartTime = 0.0 # Start time of tiling
_CkpointTime = 0.0 # Next time to print stats
_Placements = 0L # Number of placements attempted
_TBestTiling = None # Best tiling so far
_TBestScore = float(sys.maxint) # Smallest area so far
def printTilingStats():
global _CkpointTime
_CkpointTime = time.time() + 3
if _TBestTiling:
area = _TBestTiling.area()
utilization = _TBestTiling.usedArea() / area * 100.0
else:
area = 999999.0
utilization = 0.0
print "\r %ld placements / Smallest area: %.1f sq. in. / Best utilization: %.1f%%" % \
(_Placements, area, utilization),
if gerbmerge.GUI is not None:
sys.stdout.flush()
def _tile_search2(Jobs, X, Y, cfg=config.Config):
global _CkpointTime, _Placements, _TBestTiling, _TBestScore
r = random.Random()
N = len(Jobs)
# M is the number of jobs that will be placed randomly.
# N-M is the number of jobs that will be searched exhaustively.
M = N - config.RandomSearchExhaustiveJobs
M = max(M,0)
xspacing = cfg['xspacing']
yspacing = cfg['yspacing']
# Must escape with Ctrl-C
while 1:
T = tiling.Tiling(X,Y)
joborder = r.sample(range(N), N)
minInletSize = tiling.minDimension(Jobs)
for ix in joborder[:M]:
Xdim,Ydim,job,rjob = Jobs[ix]
T.removeInlets(minInletSize)
if r.choice([0,1]):
addpoints = T.validAddPoints(Xdim+xspacing,Ydim+yspacing)
if not addpoints:
break
pt = r.choice(addpoints)
T.addJob(pt, Xdim+xspacing, Ydim+yspacing, job)
else:
addpoints = T.validAddPoints(Ydim+xspacing,Xdim+yspacing)
if not addpoints:
break
pt = r.choice(addpoints)
T.addJob(pt, Ydim+xspacing, Xdim+yspacing, rjob)
else:
# Do exhaustive search on remaining jobs
if N-M:
remainingJobs = []
for ix in joborder[M:]:
remainingJobs.append(Jobs[ix])
tilesearch1.initialize(0)
tilesearch1._tile_search1(remainingJobs, T, 1)
T = tilesearch1.bestTiling()
if T:
score = T.area()
if score < _TBestScore:
_TBestTiling,_TBestScore = T,score
elif score == _TBestScore:
if T.corners() < _TBestTiling.corners():
_TBestTiling,_TBestScore = T,score
_Placements += 1
# If we've been at this for 3 seconds, print some status information
if time.time() > _CkpointTime:
printTilingStats()
# Check for timeout
if (config.SearchTimeout > 0) and ((time.time() - _StartTime) > config.SearchTimeout):
raise KeyboardInterrupt
gerbmerge.updateGUI("Performing automatic layout...")
# end while 1
def tile_search2(Jobs, X, Y):
"""Wrapper around _tile_search2 to handle keyboard interrupt, etc."""
global _StartTime, _CkpointTime, _Placements, _TBestTiling, _TBestScore
_StartTime = time.time()
_CkpointTime = _StartTime + 3
_Placements = 0L
_TBestTiling = None
_TBestScore = float(sys.maxint)
print '='*70
print "Starting random placement trials. You must press Ctrl-C to"
print "stop the process and use the best placement so far."
print "Estimated maximum possible utilization is %.1f%%." % (tiling.maxUtilization(Jobs)*100)
try:
_tile_search2(Jobs, X, Y)
printTilingStats()
print
except KeyboardInterrupt:
printTilingStats()
print
print "Interrupted."
computeTime = time.time() - _StartTime
print "Computed %ld placements in %d seconds / %.1f placements/second" % (_Placements, computeTime, _Placements/computeTime)
print '='*70
return _TBestTiling

377
gerbmerge/tiling.py Normal file
View File

@ -0,0 +1,377 @@
#!/usr/bin/env python
"""A tiling is an arrangement of jobs, where each job may
be a copy of another and may be rotated. A tiling consists
of two things:
- a list of where each job is located (the lower-left of
each job is the origin)
- a list of points that begins at (0,Ymax) and ends at
(Xmax,0). These points describe the outside boundary
of the tiling.
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
import sys
import math
import config
import jobs
# Helper functions to determine if points are right-of, left-of, above, and
# below each other. These definitions assume that points are on a line that
# is vertical or horizontal.
def left_of(p1,p2):
return p1[0]<p2[0] and p1[1]==p2[1]
def right_of(p1,p2):
return p1[0]>p2[0] and p1[1]==p2[1]
def above(p1,p2):
return p1[1]>p2[1] and p1[0]==p2[0]
def below(p1,p2):
return p1[1]<p2[1] and p1[0]==p2[0]
class Tiling:
def __init__(self, Xmax, Ymax):
# Make maximum dimensions bigger by inter-job spacing so that
# we allow jobs (which are seated at the lower left of their cells)
# to just fit on the panel, and not disqualify them because their
# spacing area slightly exceeds the panel edge.
self.xmax = Xmax + config.Config['xspacing']
self.ymax = Ymax + config.Config['yspacing']
self.points = [(0,Ymax), (0,0), (Xmax,0)] # List of (X,Y) co-ordinates
self.jobs = [] # List of 3-tuples: ((Xbl,Ybl),(Xtr,Ytr),Job) where
# (Xbl,Ybl) is bottom left, (Xtr,Ytr) is top-right of the cell.
# The actual job has dimensions (Xtr-Xbl-Config['xspacing'],Ytr-Ybl-Config['yspacing'])
# and is located at the lower-left of the cell.
def canonicalize(self, OriginX, OriginY):
"""Return a list of JobLayout objects, after setting each job's (X,Y) origin"""
L = []
for job in self.jobs:
J = jobs.JobLayout(job[2])
J.setPosition(job[0][0]+OriginX, job[0][1]+OriginY)
L.append(J)
return L
def corners(self):
return len(self.points)-2
def clone(self):
T = Tiling(self.xmax-config.Config['xspacing'], self.ymax-config.Config['yspacing'])
T.points = self.points[:]
T.jobs = self.jobs[:]
return T
def dump(self, fid=sys.stdout):
fid.write("Points:\n ")
count = 0
for XY in self.points:
fid.write("%s " % str(XY))
count += 1
if count==8:
fid.write("\n ")
count=0
if count:
fid.write("\n")
fid.write("Jobs:\n")
for bl,tr,Job in self.jobs:
fid.write(" %s: %s\n" % (str(Job), str(bl)))
def joblist(self, fid=sys.stdout):
for bl,tr,Job in self.jobs:
fid.write("%s@(%.1f,%.1f) " % (Job.name,bl[0],bl[1]))
fid.write('\n')
def isOverlap(self, ix, X, Y, cfg=config.Config):
"""Determines if a new job with actual dimensions X-by-Y located at self.points[ix]
overlaps any existing job or exceeds the boundaries of the panel.
If it's an L-point, the new job will have position
p_bl=(self.points[ix][0],self.points[ix][1])
and top-right co-ordinate:
p_tr=(self.points[ix][0]+X,self.points[ix][1]+Y)
If it's a mirror-L point, the new job will have position
p_bl=(self.points[ix][0]-X,self.points[ix][1])
and top-right co-ordinate:
p_tr=(self.points[ix][0],self.points[ix][1]+Y)
For a test job defined by t_bl and t_tr, the given job overlaps
if:
p.left_edge<t.right_edge and p.right_edge>t.left_edge
and
p.bottom_edge<t.top_edge and p.top_edge>t.bottom_edge
"""
if self.isL(ix):
p_bl = self.points[ix]
p_tr = (p_bl[0]+X, p_bl[1]+Y)
if p_tr[0]>self.xmax or p_tr[1]>self.ymax:
return 1
else:
p_bl = (self.points[ix][0]-X,self.points[ix][1])
p_tr = (self.points[ix][0],self.points[ix][1]+Y)
if p_bl[0]<0 or p_tr[1]>self.ymax:
return 1
for t_bl,t_tr,Job in self.jobs:
if p_bl[0]<t_tr[0] and p_tr[0]>t_bl[0] \
and \
p_bl[1]<t_tr[1] and p_tr[1]>t_bl[1]:
return 1
return 0
def isL(self, ix):
"""True if self.points[ix] represents an L-shaped corner where there
is free space above and to the right, like this:
+------+ _____ this point is an L-shaped corner
| | /
| +______+
| |
| |
. .
. .
. .
"""
pts = self.points
# This is an L-point if:
# Previous point X co-ordinates are the same, and
# previous point Y co-ordinate is higher, and
# next point Y co-ordinate is the same, and
# next point X co-ordinate is to the right
return pts[ix-1][0]==pts[ix][0] \
and pts[ix-1][1]>pts[ix][1] \
and pts[ix+1][1]==pts[ix][1] \
and pts[ix+1][0]>pts[ix][0]
def isMirrorL(self, ix):
"""True if self.points[ix] represents a mirrored L-shaped corner where there
is free space above and to the left, like this:
+------+
mirrored-L corner __ | |
\ | |
+______+ |
| |
| |
. .
. .
. .
"""
pts = self.points
# This is a mirrored L-point if:
# Previous point Y co-ordinates are the same, and
# previous point X co-ordinate is lower, and
# next point X co-ordinate is the same, and
# next point X co-ordinate is higher
return pts[ix-1][1]==pts[ix][1] \
and pts[ix-1][0]<pts[ix][0] \
and pts[ix+1][0]==pts[ix][0] \
and pts[ix+1][1]>pts[ix][1]
def validAddPoints(self, X, Y):
"""Return a list of all valid indices into self.points at which we can add
the job with dimensions X-by-Y). Only points which are either L-points or
mirrored-L-points and which would support the given job with no overlaps
are returned.
"""
return [ix for ix in range(1,len(self.points)-1) if (self.isL(ix) or self.isMirrorL(ix)) and not self.isOverlap(ix,X,Y)]
def mergePoints(self, ix):
"""Inspect points self.points[ix] and self.points[ix+1] as well
as self.points[ix+3] and self.points[ix+4]. If they are the same, delete
both points, thus merging lines formed when the corners of two jobs coincide.
"""
# Do farther-on points first so we can delete things right from the list
if self.points[ix+3]==self.points[ix+4]:
del self.points[ix+3:ix+5]
if self.points[ix]==self.points[ix+1]:
del self.points[ix:ix+2]
# Experimental
def removeInlets(self, minSize):
"""Find sequences of 3 points that define an "inlet", either a left/right-going gap:
...---------------+ +--------.....
| |
| |
+------------+ +------------+
| |
+----------------------+ +--------------------+
| |
. .
. .
. .
or a down-going gap: +-------.....
|
...-----------+ ...-------------+ |
| | |
| | |
| | |
| | |
| +-----.... | |
| | | |
| | | |
+--+ +--+
that are too small for any job to fit in (as defined by minSize). These inlets
can be deleted to form corners where new jobs can be placed.
"""
pt = self.points
done = 0
while not done:
# Repeat this loop each time there is a change
for ix in range(0, len(pt)-3):
# Check for horizontal left-going inlet
if right_of(pt[ix],pt[ix+1]) and above(pt[ix+1],pt[ix+2]) and left_of(pt[ix+2],pt[ix+3]):
# Make sure minSize requirement is met
if pt[ix][1]-pt[ix+3][1] < minSize:
# Get rid of middle two points, extend Y-value of highest point down to lowest point
pt[ix] = (pt[ix][0],pt[ix+3][1])
del pt[ix+1:ix+3]
break
# Check for horizontal right-going inlet
if left_of(pt[ix],pt[ix+1]) and below(pt[ix+1],pt[ix+2]) and right_of(pt[ix+2],pt[ix+3]):
# Make sure minSize requirement is met
if pt[ix+3][1]-pt[ix][1] < minSize:
# Get rid of middle two points, exten Y-value of highest point down to lowest point
pt[ix+3] = (pt[ix+3][0], pt[ix][1])
del pt[ix+1:ix+3]
break
# Check for vertical inlets
if above(pt[ix],pt[ix+1]) and left_of(pt[ix+1],pt[ix+2]) and below(pt[ix+2],pt[ix+3]):
# Make sure minSize requirement is met
if pt[ix+3][0]-pt[ix][0] < minSize:
# Is right side lower or higher?
if pt[ix+3][1]>=pt[ix][1]: # higher?
pt[ix] = (pt[ix+3][0], pt[ix][1]) # Move first point to the right
else: # lower?
pt[ix+3] = (pt[ix][0], pt[ix+3][1]) # Move last point to the left
del pt[ix+1:ix+3]
break
else:
done = 1
def addLJob(self, ix, X, Y, Job, cfg=config.Config):
"""Add a job to the tiling at L-point self.points[ix] with actual dimensions X-by-Y.
The job is added with its lower-left corner at the point. The existing point
is removed from the tiling and new points are added at the top-left, top-right
and bottom-right of the new job, with extra space added for inter-job spacing.
"""
x,y = self.points[ix]
x_tr = x+X
y_tr = y+Y
self.points[ix:ix+1] = [(x,y_tr), (x_tr,y_tr), (x_tr,y)]
self.jobs.append( ((x,y),(x_tr,y_tr),Job) )
self.mergePoints(ix-1)
def addMirrorLJob(self, ix, X, Y, Job, cfg=config.Config):
"""Add a job to the tiling at mirror-L-point self.points[ix] with dimensions X-by-Y.
The job is added with its lower-right corner at the point. The existing point
is removed from the tiling and new points are added at the bottom-left, top-left
and top-right of the new job, with extra space added for inter-job spacing.
"""
x_tr,y = self.points[ix]
x = x_tr-X
y_tr = y+Y
self.points[ix:ix+1] = [(x,y), (x,y_tr), (x_tr,y_tr)]
self.jobs.append( ((x,y),(x_tr,y_tr),Job) )
self.mergePoints(ix-1)
def addJob(self, ix, X, Y, Job):
"""Add a job to the tiling at point self.points[ix] and with dimensions X-by-Y.
If the given point is an L-point, the job will be added with its lower-left
corner at the point. If the given point is a mirrored-L point, the job will
be added with its lower-right corner at the point.
"""
if self.isL(ix):
self.addLJob(ix, X, Y, Job)
else:
self.addMirrorLJob(ix, X, Y, Job)
def bounds(self):
"""Return 2-tuple ((minX, minY), (maxX, maxY)) of rectangular region defined by all jobs"""
minX = minY = float(sys.maxint)
maxX = maxY = 0.0
for bl,tr,job in self.jobs:
minX = min(minX,bl[0])
maxX = max(maxX,tr[0])
minY = min(minY,bl[1])
maxY = max(maxY,tr[1])
return ( (minX,minY), (maxX-config.Config['xspacing'], maxY-config.Config['yspacing']) )
def area(self):
"""Return area of rectangular region defined by all jobs."""
bl,tr = self.bounds()
DX = tr[0]-bl[0]
DY = tr[1]-bl[1]
return DX*DY
def usedArea(self):
"""Return total area of just jobs, not spaces in-between."""
area = 0.0
for job in self.jobs:
area += job[2].jobarea()
return area
# Function to estimate the maximum possible utilization given a list of jobs.
# Jobs list is 4-tuple (Xdim,Ydim,job,rjob).
def maxUtilization(Jobs):
xspacing = config.Config['xspacing']
yspacing = config.Config['yspacing']
usedArea = totalArea = 0.0
for Xdim,Ydim,job,rjob in Jobs:
usedArea += job.jobarea()
totalArea += job.jobarea()
totalArea += job.width_in()*xspacing + job.height_in()*yspacing + xspacing*yspacing
# Reduce total area by strip of unused spacing around top and side. Assume
# final result will be approximately square.
sq_side = math.sqrt(totalArea)
totalArea -= sq_side*xspacing + sq_side*yspacing + xspacing*yspacing
return usedArea/totalArea
# Utility function to compute the minimum dimension along any axis of all jobs.
# Used to remove inlets.
def minDimension(Jobs):
M = float(sys.maxint)
for Xdim,Ydim,job,rjob in Jobs:
M = min(M,Xdim)
M = min(M,Ydim)
return M
# vim: expandtab ts=2 sw=2

20
gerbmerge/util.py Normal file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env python
"""
Various utility functions
--------------------------------------------------------------------
This program is licensed under the GNU General Public License (GPL)
Version 3. See http://www.fsf.org for details of the license.
Rugged Circuits LLC
http://ruggedcircuits.com/gerbmerge
"""
def in2gerb(value):
"""Convert inches to 2.5 Gerber units"""
return int(round(value*1e5))
def gerb2in(value):
"""Convert 2.5 Gerber units to inches"""
return float(value)*1e-5

3
misc/gerbmerge.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
c:\python26\python c:\python26\lib\site-packages\gerbmerge\gerbmerge.py %1 %2 %3 %4 %5 %6 %7 %8 %9

5
setup.cfg Normal file
View File

@ -0,0 +1,5 @@
[sdist]
keep_temp=1
[install]
record=installed_files

125
setup.py Normal file
View File

@ -0,0 +1,125 @@
#!/usr/bin/env python
import sys
import glob
import os
from distutils.core import setup, Extension
import distutils.sysconfig
from gerbmerge.gerbmerge import VERSION_MAJOR, VERSION_MINOR
if sys.version_info < (2,4,0):
print '*'*73
print 'GerbMerge version %d.%d requires Python 2.4 or higher' % (VERSION_MAJOR, VERSION_MINOR)
print '*'*73
sys.exit(1)
if 0:
for key,val in distutils.sysconfig.get_config_vars().items():
print key
print '***********************'
print ' ', val
print
print
sys.exit(0)
SampleFiles = glob.glob('testdata/*')
DocFiles = glob.glob('doc/*')
AuxFiles = ['COPYING']
if sys.platform == 'win32' or ('bdist_wininst' in sys.argv):
#DestLib = distutils.sysconfig.get_config_var('prefix')
#DestDir = os.path.join(DestLib, 'gerbmerge')
#BinDir = DestLib
DestLib = '.'
DestDir = os.path.join(DestLib, 'gerbmerge')
BinFiles = ['misc/gerbmerge.bat']
BinDir = '.'
else:
DestLib = distutils.sysconfig.get_config_var('LIBPYTHON')
DestDir = os.path.join(DestLib, 'gerbmerge')
BinFiles = ['misc/gerbmerge']
BinDir = distutils.sysconfig.get_config_var('BINDIR')
# Create top-level invocation program
fid = file('misc/gerbmerge', 'wt')
fid.write( \
r"""#!/bin/sh
python %s/site-packages/gerbmerge/gerbmerge.py $*
""" % DestLib)
fid.close()
dist=setup (name = "gerbmerge",
license = "GPL",
version = "%d.%d" % (VERSION_MAJOR, VERSION_MINOR),
long_description=\
r"""GerbMerge is a program that combines several Gerber
(i.e., RS274-X) and Excellon files into a single set
of files. This program is useful for combining multiple
printed circuit board layout files into a single job.
To run the program, invoke the Python interpreter on the
gerbmerge.py file. On Windows, if you installed GerbMerge in
C:/Python24, for example, open a command window (DOS box)
and type:
C:/Python24/gerbmerge.bat
For more details on installation or running GerbMerge, see the
URL below.
""",
description = "Merge multiple Gerber/Excellon files",
author = "Rugged Circuits LLC",
author_email = "support@ruggedcircuits.com",
url = "http://ruggedcircuits.com/gerbmerge",
packages = ['gerbmerge'],
platforms = ['all'],
data_files = [ (DestDir, AuxFiles),
(os.path.join(DestDir,'testdata'), SampleFiles),
(os.path.join(DestDir,'doc'), DocFiles),
(BinDir, BinFiles) ]
)
do_fix_perms = 0
if sys.platform != "win32":
for cmd in dist.commands:
if cmd[:7]=='install':
do_fix_perms = 1
break
if do_fix_perms:
# Ensure package files and misc/help files are world readable-searchable.
# Shouldn't Distutils do this for us?
print 'Setting permissions on installed files...',
try:
def fixperms(arg, dirname, names):
os.chmod(dirname, 0755)
for name in names:
fullname = os.path.join(dirname, name)
if os.access(fullname, os.X_OK):
os.chmod(fullname, 0755)
else:
os.chmod(fullname, 0644)
os.path.walk(DestDir, fixperms, 1)
os.path.walk(os.path.join(DestLib, 'site-packages/gerbmerge'), fixperms, 1)
os.chmod(os.path.join(BinDir, 'gerbmerge'), 0755)
print 'done'
except:
print 'FAILED'
print
print '*** Please verify that the installed files have correct permissions. On'
print "*** systems without permission flags, you don't need to"
print '*** worry about it.'
if cmd[:7]=='install':
print
print '******** Installation Complete ******** '
print
print 'Sample files and documentation have been installed in:'
print ' ', DestDir
print
print 'A shortcut to starting the program has been installed as:'
print ' ', os.path.join(BinDir, 'gerbmerge')
print

23
testdata/Makefile vendored Normal file
View File

@ -0,0 +1,23 @@
all: merge1.xln merge2.xln
merge1.xln:
gerbmerge layout1.cfg layout1.def
merge2.xln:
gerbmerge layout2.cfg layout2.def
view1:
gerbv merge1.* &
view2:
gerbv merge2.* &
clean:
-rm -f merge1.* merge2.* toollist.* placement.*
# Demonstrates randomized search
random:
-rm -f merge2.*
-gerbmerge layout2.cfg
# vim: noexpandtab

30
testdata/README vendored Normal file
View File

@ -0,0 +1,30 @@
This directory contains example projects for demonstrating GerbMerge.
Proj1 is a small board that has ovals, thermals, arcs, circles, polygons,
and text. Hexapod is the example board that comes with Eagle.
Each project has CAM data generated from its associated *.cam file,
hence proj1.cam and hexapod.cam were used to generate drawing data.
The "layout1.cfg" and "layout1.def" files demonstrate how GerbMerge can
be used to panelize a single job, including rotation.
The "layout2.cfg" and "layout2.def" files demonstrate panelizing
different jobs.
The Makefile in this directory shows an example of how to invoke
the program and how to view the results under Linux. You need to
have the gerbv program installed (see http://gerbv.sourceforge.net)
for viewing the output files.
The syntax for running GerbMerge is:
gerbmerge layout1.cfg layout1.def
If you are running Linux and have installed the 'gerbv' Gerber previewer,
you can view the results by typing:
gerbv *.ger *.xln
On Windows systems, GC-Prevue and ViewMate should be able to Import
the above files for previewing.

7
testdata/fabdwg.txt vendored Normal file
View File

@ -0,0 +1,7 @@
* FR4 Material
* 1 oz. copper
* All holes plated through
* Drills shown as finished sizes
* Board thickness 0.062"

157
testdata/hexapod.bor vendored Normal file
View File

@ -0,0 +1,157 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10C,0.0000*%
D10*
X000101Y000252D02*
X039471Y000252D01*
X039471Y031748D01*
X000101Y031748D01*
X000101Y000252D01*
X043351Y020502D02*
X043354Y020561D01*
X043362Y020620D01*
X043375Y020677D01*
X043394Y020734D01*
X043417Y020788D01*
X043446Y020840D01*
X043479Y020889D01*
X043516Y020935D01*
X043557Y020977D01*
X043602Y021015D01*
X043651Y021050D01*
X043702Y021079D01*
X043756Y021104D01*
X043811Y021124D01*
X043869Y021138D01*
X043927Y021148D01*
X043986Y021152D01*
X044045Y021150D01*
X044104Y021144D01*
X044162Y021132D01*
X044219Y021114D01*
X044273Y021092D01*
X044326Y021065D01*
X044376Y021033D01*
X044423Y020997D01*
X044466Y020956D01*
X044505Y020912D01*
X044540Y020865D01*
X044571Y020814D01*
X044597Y020761D01*
X044618Y020706D01*
X044634Y020649D01*
X044645Y020591D01*
X044650Y020532D01*
X044650Y020472D01*
X044645Y020413D01*
X044634Y020355D01*
X044618Y020298D01*
X044597Y020243D01*
X044571Y020190D01*
X044540Y020139D01*
X044505Y020092D01*
X044466Y020048D01*
X044423Y020007D01*
X044376Y019971D01*
X044326Y019939D01*
X044273Y019912D01*
X044219Y019890D01*
X044162Y019872D01*
X044104Y019860D01*
X044045Y019854D01*
X043986Y019852D01*
X043927Y019856D01*
X043869Y019866D01*
X043811Y019880D01*
X043756Y019900D01*
X043702Y019925D01*
X043651Y019954D01*
X043602Y019989D01*
X043557Y020027D01*
X043516Y020069D01*
X043479Y020115D01*
X043446Y020164D01*
X043417Y020216D01*
X043394Y020270D01*
X043375Y020327D01*
X043362Y020384D01*
X043354Y020443D01*
X043351Y020502D01*
X043351Y025002D02*
X043354Y025061D01*
X043362Y025120D01*
X043375Y025177D01*
X043394Y025234D01*
X043417Y025288D01*
X043446Y025340D01*
X043479Y025389D01*
X043516Y025435D01*
X043557Y025477D01*
X043602Y025515D01*
X043651Y025550D01*
X043702Y025579D01*
X043756Y025604D01*
X043811Y025624D01*
X043869Y025638D01*
X043927Y025648D01*
X043986Y025652D01*
X044045Y025650D01*
X044104Y025644D01*
X044162Y025632D01*
X044219Y025614D01*
X044273Y025592D01*
X044326Y025565D01*
X044376Y025533D01*
X044423Y025497D01*
X044466Y025456D01*
X044505Y025412D01*
X044540Y025365D01*
X044571Y025314D01*
X044597Y025261D01*
X044618Y025206D01*
X044634Y025149D01*
X044645Y025091D01*
X044650Y025032D01*
X044650Y024972D01*
X044645Y024913D01*
X044634Y024855D01*
X044618Y024798D01*
X044597Y024743D01*
X044571Y024690D01*
X044540Y024639D01*
X044505Y024592D01*
X044466Y024548D01*
X044423Y024507D01*
X044376Y024471D01*
X044326Y024439D01*
X044273Y024412D01*
X044219Y024390D01*
X044162Y024372D01*
X044104Y024360D01*
X044045Y024354D01*
X043986Y024352D01*
X043927Y024356D01*
X043869Y024366D01*
X043811Y024380D01*
X043756Y024400D01*
X043702Y024425D01*
X043651Y024454D01*
X043602Y024489D01*
X043557Y024527D01*
X043516Y024569D01*
X043479Y024615D01*
X043446Y024664D01*
X043417Y024716D01*
X043394Y024770D01*
X043375Y024827D01*
X043362Y024884D01*
X043354Y024943D01*
X043351Y025002D01*
M02*

BIN
testdata/hexapod.brd vendored Normal file

Binary file not shown.

96
testdata/hexapod.cam vendored Normal file
View File

@ -0,0 +1,96 @@
[CAM Processor Job]
Description=""
Section=Sec_1
Section=Sec_2
Section=Sec_3
Section=Sec_4
Section=Sec_5
[Sec_1]
Name="TopLayer"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack=""
Scale=1.000000
Output="hexapod.cmp"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.000000 0.000000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 1 17 18 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_2]
Name="BottomLayer"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack=""
Scale=1.000000
Output="hexapod.sol"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.000000 0.000000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 16 17 18 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_3]
Name="BoardOutline"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack=""
Scale=1.000000
Output="hexapod.bor"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.000000 0.000000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 20 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_4]
Name="Drills"
Prompt=""
Device="EXCELLON"
Wheel=""
Scale=1.000000
Output="hexapod.xln"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.020000 0.100000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 44 45 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_5]
Name="Top Silkscreen"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack=""
Scale=1.000000
Output="hexapod.plc"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.020000 0.100000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 21 25 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"

1096
testdata/hexapod.cmp vendored Normal file

File diff suppressed because it is too large Load Diff

3823
testdata/hexapod.plc vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
testdata/hexapod.sch vendored Normal file

Binary file not shown.

1413
testdata/hexapod.sol vendored Normal file

File diff suppressed because it is too large Load Diff

400
testdata/hexapod.xln vendored Normal file
View File

@ -0,0 +1,400 @@
%
M48
M72
T01C0.0197
T02C0.0320
T03C0.0360
T04C0.0400
T05C0.0440
T06C0.0520
T07C0.1300
%
T01
X6601Y8252
X6601Y9002
X14101Y11502
X14101Y12502
X18101Y12502
X18101Y11502
X21101Y12002
X21851Y12752
X21851Y13502
X26101Y10502
X26101Y9502
X26101Y8002
X26101Y7002
X30351Y9502
X29851Y11502
X29851Y12502
X28351Y19752
X28351Y23502
X19101Y27752
X18601Y27252
X17351Y26502
X16851Y25502
X16351Y26002
X13351Y25502
X12351Y26502
X11351Y26002
X10351Y25002
X9351Y23752
X6351Y23252
X6351Y25252
X6351Y26002
X14351Y23252
X33351Y2752
X33851Y2252
X31351Y2002
X30351Y1502
X29101Y2252
X18351Y2502
X17351Y2502
X14351Y2002
X13351Y2002
T02
X12351Y6502
X12351Y7502
X12351Y8502
X12351Y9502
X12351Y10502
X11351Y10502
X11351Y9502
X11351Y8502
X11351Y7502
X11351Y6502
X8351Y6502
X8351Y7502
X8351Y8502
X8351Y9502
X8351Y10502
X8351Y11502
X8351Y12502
X8351Y13502
X8851Y15502
X9851Y15502
X10851Y15502
X11851Y15502
X12851Y15502
X14351Y15502
X15351Y15502
X16351Y15502
X17351Y15502
X18351Y15502
X19351Y15502
X20351Y15502
X21351Y15502
X22851Y15502
X23851Y15502
X24851Y15502
X25851Y15502
X26851Y15502
X27851Y15502
X28851Y15502
X29851Y15502
X31351Y13502
X31351Y12502
X31351Y11502
X31351Y10502
X31351Y9502
X31351Y8502
X31351Y7502
X31351Y6502
X28351Y6502
X28351Y7502
X28351Y8502
X28351Y9502
X28351Y10502
X27351Y10502
X27351Y9502
X27351Y8502
X27351Y7502
X27351Y6502
X24351Y6502
X23351Y6502
X23351Y7502
X24351Y7502
X24351Y8502
X23351Y8502
X23351Y9502
X24351Y9502
X24351Y10502
X23351Y10502
X23351Y11502
X24351Y11502
X24351Y12502
X23351Y12502
X23351Y13502
X24351Y13502
X27351Y13502
X28351Y13502
X28351Y12502
X28351Y11502
X27351Y11502
X27351Y12502
X32851Y14752
X32851Y15752
X31851Y17502
X31851Y18502
X29851Y18502
X28851Y18502
X27851Y18502
X26851Y18502
X25851Y18502
X24851Y18502
X23851Y18502
X22851Y18502
X21351Y18502
X20351Y18502
X19351Y18502
X18351Y18502
X17351Y18502
X16351Y18502
X15351Y18502
X14351Y18502
X12851Y18502
X11851Y18502
X10851Y18502
X9851Y18502
X8851Y18502
X7851Y18502
X6851Y18502
X5851Y18502
X5851Y15502
X6851Y15502
X7851Y15502
X5601Y13502
X5601Y12502
X11351Y12502
X12351Y12502
X12351Y13502
X11351Y13502
X11351Y11502
X12351Y11502
X15351Y11502
X16351Y11502
X16351Y12502
X15351Y12502
X15351Y13502
X16351Y13502
X19351Y13502
X20351Y13502
X20351Y12502
X19351Y12502
X19351Y11502
X20351Y11502
X20351Y10502
X19351Y10502
X19351Y9502
X20351Y9502
X20351Y8502
X19351Y8502
X19351Y7502
X20351Y7502
X20351Y6502
X19351Y6502
X16351Y6502
X15351Y6502
X15351Y7502
X16351Y7502
X16351Y8502
X15351Y8502
X15351Y9502
X16351Y9502
X16351Y10502
X15351Y10502
X5601Y7502
X5601Y6502
X30351Y24252
X30351Y26252
X34351Y5502
X35351Y5502
X36351Y5502
X37351Y5502
X38351Y5502
X38351Y1502
X37351Y1502
X36351Y1502
X35351Y1502
X34351Y1502
T03
X1851Y3002
X2851Y3002
X2851Y4002
X1851Y4002
X1851Y5002
X2851Y5002
X2851Y6002
X1851Y6002
X1851Y7002
X2851Y7002
X2851Y8002
X1851Y8002
X1851Y9002
X2851Y9002
X2851Y10002
X1851Y10002
X1851Y17002
X2851Y17002
X2851Y18002
X1851Y18002
X1851Y19002
X2851Y19002
X2851Y20002
X1851Y20002
X1851Y21002
X2851Y21002
X2851Y22002
X1851Y22002
X1851Y23002
X2851Y23002
X2851Y24002
X1851Y24002
X1851Y25002
X2851Y25002
X2851Y26002
X1851Y26002
X1851Y27002
X2851Y27002
X2851Y28002
X1851Y28002
X1851Y29002
X2851Y29002
X36351Y15252
X37351Y15252
X37351Y14252
X36351Y14252
X36351Y13252
X37351Y13252
X37351Y12252
X36351Y12252
X36351Y11252
X37351Y11252
X37351Y10252
X36351Y10252
X36351Y9252
X37351Y9252
X37351Y8252
X36351Y8252
T04
X31851Y4502
X30851Y4502
X29851Y4502
X28851Y4502
X27851Y4502
X26851Y4502
X25851Y4502
X24851Y4502
X23851Y4502
X22851Y4502
X21851Y4502
X20851Y4502
X19851Y4502
X18851Y4502
X17851Y4502
X16851Y4502
X15851Y4502
X14851Y4502
X13851Y4502
X12851Y4502
X11851Y4502
X10851Y4502
X9851Y4502
X8851Y4502
X7851Y4502
X7851Y3502
X8851Y3502
X9851Y3502
X10851Y3502
X11851Y3502
X12851Y3502
X13851Y3502
X14851Y3502
X15851Y3502
X16851Y3502
X17851Y3502
X18851Y3502
X19851Y3502
X20851Y3502
X21851Y3502
X22851Y3502
X23851Y3502
X24851Y3502
X25851Y3502
X26851Y3502
X27851Y3502
X28851Y3502
X29851Y3502
X30851Y3502
X31851Y3502
X31851Y21502
X30851Y21502
X29851Y21502
X28851Y21502
X27851Y21502
X27851Y22502
X28851Y22502
X29851Y22502
X30851Y22502
X31851Y22502
X26851Y22502
X25851Y22502
X24851Y22502
X23851Y22502
X22851Y22502
X22851Y21502
X23851Y21502
X24851Y21502
X25851Y21502
X26851Y21502
X21851Y21502
X20851Y21502
X19851Y21502
X18851Y21502
X17851Y21502
X17851Y22502
X18851Y22502
X19851Y22502
X20851Y22502
X21851Y22502
X16851Y22502
X15851Y22502
X14851Y22502
X13851Y22502
X12851Y22502
X12851Y21502
X13851Y21502
X14851Y21502
X15851Y21502
X16851Y21502
X11851Y21502
X10851Y21502
X9851Y21502
X8851Y21502
X7851Y21502
X7851Y22502
X8851Y22502
X9851Y22502
X10851Y22502
X11851Y22502
X21601Y28752
X21601Y29752
T05
X20351Y27502
X6351Y27502
X32851Y13502
X32851Y9502
X37101Y19502
X37101Y20502
X37101Y21502
X37101Y24002
X37101Y25002
X37101Y26002
T06
X35191Y29502
X32241Y29502
X29291Y29502
X25351Y29502
T07
X44001Y25002
X44001Y20502
M30

272
testdata/layout1.cfg vendored Normal file
View File

@ -0,0 +1,272 @@
# This configuration file demonstrates panelizing a single job.
##############################################################################
# In the [DEFAULT] section you can create global names to save typing the same
# directory name, for example, over and over.
##############################################################################
[DEFAULT]
# Change projdir to wherever your project files are, for example:
#
# projdir = /home/stuff/projects/test
#
# or relative pathname from where you are running GerbMerge
#
# projdir = testdata
#
# or if all files are in the current directory (as in this example):
#
# projdir = .
projdir = .
# For convenience, this is the base name of the merged output files.
MergeOut = merge1
#############################################################################
# The [Options] section defines settings that control how the input files are
# read and how the output files are generated.
#############################################################################
[Options]
################################################################
#
# Settings that are very important
#
################################################################
# Option indicating name of file that maps Excellon tool codes to drill sizes.
# This is not necessary if the Excellon files have embedded tool sizes, or if a
# tool list is specified as part of the job description. The ToolList option
# here is the "last resort" for mapping tool codes to tool sizes. Most recent
# PCB programs embed drill size information right in the Excellon file, so this
# option should not be necessary and can be commented out.
#ToolList=proj1.drl
# Optional indication of the number of decimal places in input Excellon drill
# files. The default is 4 which works for recent versions of Eagle (since
# version 4.11r12), as well as Orcad and PCB. Older versions of Eagle use 3
# decimal places.
#ExcellonDecimals = 4
################################################################
#
# Settings that are somewhat important
#
################################################################
# Which layers to draw cut lines on. Omit this option or set to 'None' for no
# cut lines. Cut lines are borders around each job that serve as guides for
# cutting the panel into individual jobs. Option 'CutLineWidth' sets the
# thickness of these cut lines.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CutLineLayers = *topsilkscreen,*bottomsilkscreen
# Which layers to draw crop marks on. Omit this option or set to 'None' for no
# crop marks. Crop marks are small L-shaped marks at the 4 corners of the final
# panel. These practically define the extents of the panel and are required by
# some board manufacturers. Crop marks are also required if you want to leave
# extra space around the final panel for tooling or handling. Option
# 'CropMarkWidth' sets the thickness of these crop marks.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CropMarkLayers = *topsilkscreen,*bottomsilkscreen
# Set this option to the name of a file in which to write a Gerber fabrication
# drawing. Some board manufacturers require a fabrication drawing with panel
# dimensions and drill hit marks and drill legend. There's no harm in creating
# this file...you can ignore it if you don't need it.
FabricationDrawingFile = %(mergeout)s.fab
# If FabricationDrawingFile is specified, you can provide an optional file name
# of a file containing arbitrary text to add to the fabrication drawing. This
# text can indicate manufacturing information, contact information, etc.
#FabricationDrawingText = %(projdir)s/fabdwg.txt
# Option to generate leading zeros in the output Excellon drill file, i.e., to
# NOT use leading-zero suppression. Some Gerber viewers cannot properly guess
# the Excellon file format when there are no leading zeros. Set this option to
# 1 if your Gerber viewer is putting the drill holes in far off places that do
# not line up with component pads.
ExcellonLeadingZeros = 0
# Optional additional Gerber layer on which to draw a rectangle defining the
# extents of the entire panelized job. This will create a Gerber file (with
# name specified by this option) that simply contains a rectangle defining the
# outline of the final panel. This outline file is useful for circuit board
# milling to indicate a path for the router tool. There's no harm in creating
# this file...you can ignore it if you don't need it.
OutlineLayerFile = %(mergeout)s.oln
# Optional additional Gerber layer on which to draw horizontal and vertical
# lines describing where to score (i.e., V-groove) the panel so that jobs
# can easily snap apart. These scoring lines will be drawn half-way between
# job borders.
ScoringFile = %(mergeout)s.sco
# Set the maximum dimensions of the final panel, if known. You can set the
# dimensions of the maximum panel size supported by your board manufacturer,
# and GerbMerge will print an error message if your layout exceeds these
# dimensions. Alternatively, when using automatic placement, the panel sizes
# listed here constrain the random placements such that only placements that
# fit within the given panel dimensions will be considered. The dimensions are
# specified in inches.
PanelWidth = 12.6
PanelHeight = 7.8
# Set the amount of extra space to leave around the edges of the panel to
# simplify tooling and handling. These margins are specified in inches, and
# default to 0" if not specified. These spacings will only be visible to the
# board manufacturer if you enable crop marks (see CropMarkLayers above) or use.
LeftMargin = 0.1
RightMargin = 0.1
TopMargin = 0.1
BottomMargin = 0.1
################################################################
#
# Settings that are probably not important
#
################################################################
# Set the inter-job spacing (inches) in both the X-dimension (width) and
# Y-dimension (height). Normally these would be the same unless you're trying
# really hard to make your jobs fit into a panel of exact size and you need to
# tweak these spacings to make it work. 0.125" is probably generous, about half
# that is practical for using a band saw, but you probably want to leave it at
# 0.125" if you have copper features close to the board edges and/or are using
# less precise tools, like a hacksaw, for separating the boards.
XSpacing = 0.125
YSpacing = 0.125
# Width of cut lines, in inches. The default value is 0.01". These are drawn on
# the layers specified by CutLineLayers.
CutLineWidth = 0.01
# Width of crop marks, in inches. The default value is 0.01". These are drawn on
# the layers specified by CropMarkLayers.
CropMarkWidth = 0.01
# This option is intended to reduce the probability of forgetting to include a
# layer in a job description when panelizing two or more different jobs.
# Unless this option is set to 1, an error will be raised if some jobs do not
# have the same layer names as the others, i.e., are missing layers. For
# example, if one job has a top-side soldermask layer and another doesn't, that
# could be a mistake. Setting this option to 1 prevents this situation from
# raising an error.
AllowMissingLayers = 0
# This option is intended to reduce the number of drills in the output by
# eliminating drill sizes that are too close to make a difference. For example,
# it probably does not make sense to have two separate 0.031" and 0.0315"
# drills. The DrillClusterTolerance value specifies how much tolerance is
# allowed in drill sizes, in units of inches. Multiple drill tools that span
# twice this tolerance will be clustered into a single drill tool. For example,
# a set of 0.031", 0.0315", 0.032", and 0.034" drills will all be replaced by a
# single drill tool of diameter (0.031"+0.034")/2 = 0.0325". It is guaranteed
# that all original drill sizes will be no farther than DrillClusterTolerance
# from the drill tool size generated by clustering.
#
# Setting DrillClusterTolerance to 0 disables clustering.
DrillClusterTolerance = 0.002
# Use this option to automatically thicken features on particular layers. This
# is intended for thickening silkscreen to some minimum width. The value of
# this option must be a comma-separated list of layer names followed by minimum
# feature sizes (in inches) for that layer. Comment this out to disable thickening.
MinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008
##############################################################################
# This section sets the name of merged output files. Each assignment below
# specifies a layer name and the file name that is to be written for that
# merged layer. Except for the BoardOutline and Drills layer names, all other
# layer names must begin with an asterisk '*'. The special layer name Placement
# is used to specify the placement file that can be used with the
# '--place-file' command-line option in a future invocation of GerbMerge. The
# special layer name ToolList is used to specify the file name that represents
# the tool list for the panelized job.
#
# By default, if this section is omitted or no layername=filename assignment is
# made, the following files are generated:
#
# BoardOutline = merged.boardoutline.ger
# Drills = merged.drills.xln
# Placement = merged.placement.txt
# ToolList = merged.toollist.drl
# *layername = merged.layername.ger
# (for example: 'merged.toplayer.ger', 'merged.silkscreen.ger')
#
# Any assignment that does not begin with '*' or is not one of the reserved
# names BoardOutline, Drills, ToolList, or Placement is a generic string
# assignment that can be used for string substitutions, to save typing.
##############################################################################
[MergeOutputFiles]
Prefix = %(mergeout)s
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
ToolList = toollist.%(prefix)s.drl
Placement = placement.%(prefix)s.txt
##############################################################################
# The remainder of the file specifies the jobs to be panelized. Each job is
# specified in its own section. To each job you can assign a job name, which
# will be the name of the section in square brackets (e.g., [Proj1]). This job
# name is used in the layout file (if used) to refer to the job.
#
# Job names are case-sensitive, but do not create job names that are the same
# except for the case of the characters, as this may cause problems during
# layout. Job names may only contain the following characters:
#
# a-z A-Z 0-9 _
#
# In addition, job names must begin with a letter (a-z or A-Z).
##############################################################################
[Proj1]
# You can set any options you like to make generating filenames easier, like
# Prefix. This is just a helper option, not a reserved name. Note, however,
# that you must write %(prefix)s below, in ALL LOWERCASE.
#
# Note how we are making use of the 'projdir' string defined way up at the top
# in the [DEFAULT] section to save some typing. By setting 'projdir=somedir'
# the expression '%(projdir)s/proj1' expands to 'somedir/proj1'.
Prefix=%(projdir)s/proj1
# List all the layers that participate in this job. Required layers are Drills
# and BoardOutline and have no '*' at the beginning. Optional layers have
# names chosen by you and begin with '*'. You should choose consistent layer
# names across all jobs.
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
# If this job does not have drill tool sizes embedded in the Excellon file, it
# needs to have a separate tool list file that maps tool names (e.g., 'T01') to
# tool diameter. This may be the global tool list specified in the [Options]
# section with the ToolList parameter. If this job doesn't have embedded tool
# sizes, and uses a different tool list than the global one, you can specify it
# here.
#ToolList=proj1.drl
# If this job has a different ExcellonDecimals setting than the global setting
# in the [Options] section above, it can be overridden here.
#ExcellonDecimals = 3
# You can set a 'Repeat' parameter for this job when using automatic placement
# (i.e., no *.def file) to indicate how many times this job should appear in
# the final panel. When using manual placement, this option is ignored.
#Repeat = 5

19
testdata/layout1.def vendored Normal file
View File

@ -0,0 +1,19 @@
# This example simply takes the small Proj1 board and panelizes
# it in a 2x4 array. To demonstrate rotation, the second column
# consists of rotated jobs. You wouldn't really do it this way,
# of course, as it wastes space.
Row {
Col {
Proj1
Proj1
Proj1
Proj1
}
Col {
Proj1 Rotate90
Proj1 Rotate180
Proj1 Rotate270
Proj1 Rotate
}
}

282
testdata/layout2.cfg vendored Normal file
View File

@ -0,0 +1,282 @@
# This configuration file demonstrates panelizing multiple, different jobs.
# We panelize the HEXAPOD job and several copies of the Proj1 job.
##############################################################################
# In the [DEFAULT] section you can create global names to save typing the same
# directory name, for example, over and over.
##############################################################################
[DEFAULT]
# Change projdir to wherever your project files are, for example:
#
# projdir = /home/stuff/projects/test
#
# or relative pathname from where you are running GerbMerge
#
# projdir = testdata
#
# or if all files are in the current directory (as in this example):
#
# projdir = .
projdir = .
# For convenience, this is the base name of the merged output files.
MergeOut = merge2
#############################################################################
# The [Options] section defines settings that control how the input files are
# read and how the output files are generated.
#############################################################################
[Options]
################################################################
#
# Settings that are very important
#
################################################################
# Option indicating name of file that maps Excellon tool codes to drill sizes.
# This is not necessary if the Excellon files have embedded tool sizes, or if a
# tool list is specified as part of the job description. The ToolList option
# here is the "last resort" for mapping tool codes to tool sizes. Most recent
# PCB programs embed drill size information right in the Excellon file, so this
# option should not be necessary and can be commented out.
#ToolList=proj1.drl
# Optional indication of the number of decimal places in input Excellon drill
# files. The default is 4 which works for recent versions of Eagle (since
# version 4.11r12), as well as Orcad and PCB. Older versions of Eagle use 3
# decimal places.
#ExcellonDecimals = 4
################################################################
#
# Settings that are somewhat important
#
################################################################
# Which layers to draw cut lines on. Omit this option or set to 'None' for no
# cut lines. Cut lines are borders around each job that serve as guides for
# cutting the panel into individual jobs. Option 'CutLineWidth' sets the
# thickness of these cut lines.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CutLineLayers = *topsilkscreen,*bottomsilkscreen
# Which layers to draw crop marks on. Omit this option or set to 'None' for no
# crop marks. Crop marks are small L-shaped marks at the 4 corners of the final
# panel. These practically define the extents of the panel and are required by
# some board manufacturers. Crop marks are also required if you want to leave
# extra space around the final panel for tooling or handling. Option
# 'CropMarkWidth' sets the thickness of these crop marks.
#
# NOTE: Layer names are ALL LOWERCASE, even if you define them with uppercase
# letters below.
CropMarkLayers = *topsilkscreen,*bottomsilkscreen
# Set this option to the name of a file in which to write a Gerber fabrication
# drawing. Some board manufacturers require a fabrication drawing with panel
# dimensions and drill hit marks and drill legend. There's no harm in creating
# this file...you can ignore it if you don't need it.
FabricationDrawingFile = %(mergeout)s.fab
# If FabricationDrawingFile is specified, you can provide an optional file name
# of a file containing arbitrary text to add to the fabrication drawing. This
# text can indicate manufacturing information, contact information, etc.
FabricationDrawingText = %(projdir)s/fabdwg.txt
# Option to generate leading zeros in the output Excellon drill file, i.e., to
# NOT use leading-zero suppression. Some Gerber viewers cannot properly guess
# the Excellon file format when there are no leading zeros. Set this option to
# 1 if your Gerber viewer is putting the drill holes in far off places that do
# not line up with component pads.
ExcellonLeadingZeros = 0
# Optional additional Gerber layer on which to draw a rectangle defining the
# extents of the entire panelized job. This will create a Gerber file (with
# name specified by this option) that simply contains a rectangle defining the
# outline of the final panel. This outline file is useful for circuit board
# milling to indicate a path for the router tool. There's no harm in creating
# this file...you can ignore it if you don't need it.
OutlineLayerFile = %(mergeout)s.oln
# Optional additional Gerber layer on which to draw horizontal and vertical
# lines describing where to score (i.e., V-groove) the panel so that jobs
# can easily snap apart. These scoring lines will be drawn half-way between
# job borders.
ScoringFile = %(mergeout)s.sco
# Set the maximum dimensions of the final panel, if known. You can set the
# dimensions of the maximum panel size supported by your board manufacturer,
# and GerbMerge will print an error message if your layout exceeds these
# dimensions. Alternatively, when using automatic placement, the panel sizes
# listed here constrain the random placements such that only placements that
# fit within the given panel dimensions will be considered. The dimensions are
# specified in inches.
PanelWidth = 12.6
PanelHeight = 7.8
# Set the amount of extra space to leave around the edges of the panel to
# simplify tooling and handling. These margins are specified in inches, and
# default to 0" if not specified. These spacings will only be visible to the
# board manufacturer if you enable crop marks (see CropMarkLayers above) or use
# an OutlineLayer.
LeftMargin = 0.1
RightMargin = 0.1
TopMargin = 0.1
BottomMargin = 0.1
################################################################
#
# Settings that are probably not important
#
################################################################
# Set the inter-job spacing (inches) in both the X-dimension (width) and
# Y-dimension (height). Normally these would be the same unless you're trying
# really hard to make your jobs fit into a panel of exact size and you need to
# tweak these spacings to make it work. 0.125" is probably generous, about half
# that is practical for using a band saw, but you probably want to leave it at
# 0.125" if you have copper features close to the board edges and/or are using
# less precise tools, like a hacksaw, for separating the boards.
XSpacing = 0.125
YSpacing = 0.125
# Width of cut lines, in inches. The default value is 0.01". These are drawn on
# the layers specified by CutLineLayers.
CutLineWidth = 0.01
# Width of crop marks, in inches. The default value is 0.01". These are drawn on
# the layers specified by CropMarkLayers.
CropMarkWidth = 0.01
# This option is intended to reduce the probability of forgetting to include a
# layer in a job description when panelizing two or more different jobs.
# Unless this option is set to 1, an error will be raised if some jobs do not
# have the same layer names as the others, i.e., are missing layers. For
# example, if one job has a top-side soldermask layer and another doesn't, that
# could be a mistake. Setting this option to 1 prevents this situation from
# raising an error.
AllowMissingLayers = 1
# This option is intended to reduce the number of drills in the output by
# eliminating drill sizes that are too close to make a difference. For example,
# it probably does not make sense to have two separate 0.031" and 0.0315"
# drills. The DrillClusterTolerance value specifies how much tolerance is
# allowed in drill sizes, in units of inches. Multiple drill tools that span
# twice this tolerance will be clustered into a single drill tool. For example,
# a set of 0.031", 0.0315", 0.032", and 0.034" drills will all be replaced by a
# single drill tool of diameter (0.031"+0.034")/2 = 0.0325". It is guaranteed
# that all original drill sizes will be no farther than DrillClusterTolerance
# from the drill tool size generated by clustering.
#
# Setting DrillClusterTolerance to 0 disables clustering.
DrillClusterTolerance = 0.002
# Use this option to automatically thicken features on particular layers. This
# is intended for thickening silkscreen to some minimum width. The value of
# this option must be a comma-separated list of layer names followed by minimum
# feature sizes (in inches) for that layer. Comment this out to disable thickening.
MinimumFeatureSize = *topsilkscreen,0.008,*bottomsilkscreen,0.008
##############################################################################
# This section sets the name of merged output files. Each assignment below
# specifies a layer name and the file name that is to be written for that
# merged layer. Except for the BoardOutline and Drills layer names, all other
# layer names must begin with an asterisk '*'. The special layer name Placement
# is used to specify the placement file that can be used with the
# '--place-file' command-line option in a future invocation of GerbMerge. The
# special layer name ToolList is used to specify the file name that represents
# the tool list for the panelized job.
#
# By default, if this section is omitted or no layername=filename assignment is
# made, the following files are generated:
#
# BoardOutline = merged.boardoutline.ger
# Drills = merged.drills.xln
# Placement = merged.placement.txt
# ToolList = merged.toollist.drl
# *layername = merged.layername.ger
# (for example: 'merged.toplayer.ger', 'merged.silkscreen.ger')
#
# Any assignment that does not begin with '*' or is not one of the reserved
# names BoardOutline, Drills, ToolList, or Placement is a generic string
# assignment that can be used for string substitutions, to save typing.
##############################################################################
[MergeOutputFiles]
Prefix = %(mergeout)s
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
ToolList = toollist.%(prefix)s.drl
Placement = placement.%(prefix)s.txt
##############################################################################
# The remainder of the file specifies the jobs to be panelized. Each job is
# specified in its own section. To each job you can assign a job name, which
# will be the name of the section in square brackets (e.g., [Proj1]). This job
# name is used in the layout file (if used) to refer to the job.
#
# Job names are case-sensitive, but do not create job names that are the same
# except for the case of the characters, as this may cause problems during
# layout. Job names may only contain the following characters:
#
# a-z A-Z 0-9 _
#
# In addition, job names must begin with a letter (a-z or A-Z).
##############################################################################
[Proj1]
# You can set any options you like to make generating filenames easier, like
# Prefix. This is just a helper option, not a reserved name. Note, however,
# that you must write %(prefix)s below, in ALL LOWERCASE.
#
# Note how we are making use of the 'projdir' string defined way up at the top
# in the [DEFAULT] section to save some typing. By setting 'projdir=somedir'
# the expression '%(projdir)s/proj1' expands to 'somedir/proj1'.
Prefix=%(projdir)s/proj1
# List all the layers that participate in this job. Required layers are Drills
# and BoardOutline and have no '*' at the beginning. Optional layers have
# names chosen by you and begin with '*'. You should choose consistent layer
# names across all jobs.
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
*BottomSilkscreen=%(prefix)s.pls
*TopSoldermask=%(prefix)s.stc
*BottomSoldermask=%(prefix)s.sts
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor
# If this job does not have drill tool sizes embedded in the Excellon file, it
# needs to have a separate tool list file that maps tool names (e.g., 'T01') to
# tool diameter. This may be the global tool list specified in the [Options]
# section with the ToolList parameter. If this job doesn't have embedded tool
# sizes, and uses a different tool list than the global one, you can specify it
# here.
#ToolList=proj1.drl
# If this job has a different ExcellonDecimals setting than the global setting
# in the [Options] section above, it can be overridden here.
#ExcellonDecimals = 3
# You can set a 'Repeat' parameter for this job when using automatic placement
# (i.e., no *.def file) to indicate how many times this job should appear in
# the final panel. When using manual placement, this option is ignored.
Repeat = 11
[Hexapod]
Prefix=%(projdir)s/hexapod
*TopLayer=%(prefix)s.cmp
*BottomLayer=%(prefix)s.sol
*TopSilkscreen=%(prefix)s.plc
Drills=%(prefix)s.xln
BoardOutline=%(prefix)s.bor

57
testdata/layout2.def vendored Normal file
View File

@ -0,0 +1,57 @@
# This layout merges a Hexapod and Proj1 boards into a single
# panel. The layout demonstrates nested rows and columns. The
# final arrangement looks like this (make sure you are looking
# at this document with a fixed-width font like Courier):
#
# +-----------------------------------------------------+
# | Proj1 | Proj1 | Proj1 | Proj1 | Proj1 | |
# | | | | | | |
# | | | | | | |
# +---------+---------+---------+---------+--------+ |
# | |
# | +-------+-------+
# | | P | P |
# +--------------------------------+ | r | r |
# | | | o | o |
# | | | j | j |
# | | | 1 | 1 |
# | | +-------+-------+
# | | | P | P |
# | | | r | r |
# | | | o | o |
# | Hexapod | | j | j |
# | | | 1 | 1 |
# | | +-------+-------+
# | | | P | P |
# | | | r | r |
# | | | o | o |
# | | | j | j |
# | | | 1 | 1 |
# +--------------------------------+----+-------+-------+
Row { // First row has the hexapod and 2x3 panel of
// rotated Proj1 jobs.
Hexapod
Col { // Could also write this as two separate 1x3 columns
Row { // First 1x2 row
Proj1 Rotate
Proj1 Rotate
}
Row { // Second 1x2 row above first one
Proj1 Rotate
Proj1 Rotate
}
Row { // Third 1x2 row above second row
Proj1 Rotate
Proj1 Rotate
}
} // end of column
} // end of first row
Row { // Second row has 5x1 panel of Proj1
Proj1
Proj1
Proj1
Proj1
Proj1
}

52
testdata/proj1.bor vendored Normal file
View File

@ -0,0 +1,52 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10C,0.0000*%
D10*
X000740Y000477D02*
X000740Y009873D01*
X011210Y009873D01*
X011210Y000477D01*
X000740Y000477D01*
X005505Y005625D02*
X005507Y005647D01*
X005513Y005668D01*
X005523Y005688D01*
X005536Y005706D01*
X005553Y005721D01*
X005572Y005732D01*
X005592Y005740D01*
X005614Y005744D01*
X005636Y005744D01*
X005658Y005740D01*
X005678Y005732D01*
X005697Y005721D01*
X005714Y005706D01*
X005727Y005688D01*
X005737Y005668D01*
X005743Y005647D01*
X005745Y005625D01*
X005743Y005603D01*
X005737Y005582D01*
X005727Y005562D01*
X005714Y005544D01*
X005697Y005529D01*
X005678Y005518D01*
X005658Y005510D01*
X005636Y005506D01*
X005614Y005506D01*
X005592Y005510D01*
X005572Y005518D01*
X005553Y005529D01*
X005536Y005544D01*
X005523Y005562D01*
X005513Y005582D01*
X005507Y005603D01*
X005505Y005625D01*
M02*

BIN
testdata/proj1.brd vendored Normal file

Binary file not shown.

153
testdata/proj1.cam vendored Normal file
View File

@ -0,0 +1,153 @@
[CAM Processor Job]
Description=""
Section=Sec_1
Section=Sec_2
Section=Sec_3
Section=Sec_4
Section=Sec_5
Section=Sec_6
Section=Sec_7
Section=Sec_8
[Sec_1]
Name="TopLayer"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack=""
Scale=1.000000
Output="proj1.cmp"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.000000 0.000000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 1 17 18 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_2]
Name="BottomLayer"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack=""
Scale=1.000000
Output="proj1.sol"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.000000 0.000000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 16 17 18 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_3]
Name="BoardOutline"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack=""
Scale=1.000000
Output="proj1.bor"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.000000 0.000000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 20 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_4]
Name="Drills"
Prompt=""
Device="EXCELLON"
Wheel=""
Scale=1.000000
Output="proj1.xln"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.020000 0.100000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 44 45 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_5]
Name="Top Soldermask"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack="proj1.drl"
Scale=1.000000
Output="proj1.stc"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.020000 0.100000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 29 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_6]
Name="Bottom Soldermask"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack="proj1.drl"
Scale=1.000000
Output="proj1.sts"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.020000 0.100000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 30 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_7]
Name="Top Silkscreen"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack="proj1.drl"
Scale=1.000000
Output="proj1.plc"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.020000 0.100000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 21 25 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"
[Sec_8]
Name="Bottom Silkscreen"
Prompt=""
Device="GERBER_RS274X"
Wheel=""
Rack="proj1.drl"
Scale=1.000000
Output="proj1.pls"
Flags="0 0 0 1 0 1 1"
Emulate="0 0 0"
Offset="0.0mil 0.0mil"
Sheet=1
Tolerance="0.000000 0.000000 0.000000 0.000000 0.020000 0.100000"
Pen="0.0mil 0.000000"
Page="12000.0mil 8000.0mil"
Layers=" 22 26 50 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 252 253 255"
Colors=" 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 6 6 4 8 8 8 8 8 8 8 8 8 8 8 8 8 4 4 1 1 1 1 3 3 1 2 6 8 8 5 8 8 8 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 2 4 3 6 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0"

765
testdata/proj1.cmp vendored Normal file
View File

@ -0,0 +1,765 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10C,0.0060*%
%ADD11R,0.1500X0.1000*%
%ADD12C,0.0160*%
%ADD13OC8,0.0850*%
%ADD14O,0.0780X0.1560*%
%ADD15O,0.1560X0.0780*%
%ADD16O,0.1200X0.0600*%
%ADD17C,0.0400*%
%ADD18R,0.0400X0.0400*%
%ADD19C,0.0020*%
D10*
X004768Y008755D02*
X004768Y009395D01*
X004555Y009395D02*
X004982Y009395D01*
X005199Y009075D02*
X005306Y009182D01*
X005520Y009182D01*
X005626Y009075D01*
X005626Y008968D01*
X005199Y008968D01*
X005199Y008862D02*
X005199Y009075D01*
X005199Y008862D02*
X005306Y008755D01*
X005520Y008755D01*
X005844Y008755D02*
X006271Y009182D01*
X006488Y009182D02*
X006702Y009182D01*
X006595Y009289D02*
X006595Y008862D01*
X006702Y008755D01*
X006271Y008755D02*
X005844Y009182D01*
D11*
X002375Y009125D03*
D12*
X007225Y006925D02*
X009725Y006925D01*
X009725Y004925D01*
X007225Y004925D01*
X007225Y006925D01*
X007225Y006808D02*
X009725Y006808D01*
X009725Y006650D02*
X007225Y006650D01*
X007225Y006491D02*
X007935Y006491D01*
X007974Y006530D02*
X007620Y006175D01*
X007620Y005975D01*
X008175Y005975D01*
X008175Y006530D01*
X007974Y006530D01*
X008175Y006491D02*
X008275Y006491D01*
X008275Y006530D02*
X008475Y006530D01*
X008830Y006175D01*
X008830Y005975D01*
X008275Y005975D01*
X008275Y005875D01*
X008830Y005875D01*
X008830Y005674D01*
X008475Y005320D01*
X008275Y005320D01*
X008275Y005875D01*
X008175Y005875D01*
X008175Y005320D01*
X007974Y005320D01*
X007620Y005674D01*
X007620Y005875D01*
X008175Y005875D01*
X008175Y005975D01*
X008275Y005975D01*
X008275Y006530D01*
X008275Y006332D02*
X008175Y006332D01*
X008175Y006174D02*
X008275Y006174D01*
X008275Y006015D02*
X008175Y006015D01*
X008175Y005857D02*
X008275Y005857D01*
X008275Y005698D02*
X008175Y005698D01*
X008175Y005540D02*
X008275Y005540D01*
X008275Y005381D02*
X008175Y005381D01*
X007913Y005381D02*
X007225Y005381D01*
X007225Y005223D02*
X009725Y005223D01*
X009725Y005381D02*
X008537Y005381D01*
X008695Y005540D02*
X009725Y005540D01*
X009725Y005698D02*
X008830Y005698D01*
X008830Y005857D02*
X009725Y005857D01*
X009725Y006015D02*
X008830Y006015D01*
X008830Y006174D02*
X009725Y006174D01*
X009725Y006332D02*
X008673Y006332D01*
X008514Y006491D02*
X009725Y006491D01*
X009765Y007845D02*
X009625Y008125D01*
X009625Y008625D01*
X010009Y009393D01*
X010241Y009393D01*
X010625Y008625D01*
X010625Y008125D01*
X010485Y007845D01*
X010485Y008291D01*
X010407Y008369D01*
X010447Y008410D01*
X010505Y008549D01*
X010505Y008625D01*
X010505Y008700D01*
X010447Y008840D01*
X010340Y008947D01*
X010200Y009005D01*
X010125Y009005D01*
X010125Y008625D01*
X010125Y008625D01*
X010505Y008625D01*
X010125Y008625D01*
X010125Y008625D01*
X010125Y009005D01*
X010049Y009005D01*
X009910Y008947D01*
X009803Y008840D01*
X009745Y008700D01*
X009745Y008625D01*
X010124Y008625D01*
X010124Y008625D01*
X009745Y008625D01*
X009745Y008549D01*
X009803Y008410D01*
X009843Y008369D01*
X009765Y008291D01*
X009765Y007845D01*
X009765Y007918D02*
X009728Y007918D01*
X009765Y008076D02*
X009649Y008076D01*
X009625Y008235D02*
X009765Y008235D01*
X009819Y008394D02*
X009625Y008394D01*
X009625Y008552D02*
X009745Y008552D01*
X009749Y008711D02*
X009668Y008711D01*
X009747Y008869D02*
X009832Y008869D01*
X009826Y009028D02*
X010423Y009028D01*
X010418Y008869D02*
X010503Y008869D01*
X010501Y008711D02*
X010582Y008711D01*
X010625Y008552D02*
X010505Y008552D01*
X010431Y008394D02*
X010625Y008394D01*
X010625Y008235D02*
X010485Y008235D01*
X010485Y008076D02*
X010601Y008076D01*
X010521Y007918D02*
X010485Y007918D01*
X010125Y008711D02*
X010125Y008711D01*
X010125Y008869D02*
X010125Y008869D01*
X009906Y009186D02*
X010344Y009186D01*
X010265Y009345D02*
X009985Y009345D01*
X007777Y006332D02*
X007225Y006332D01*
X007225Y006174D02*
X007620Y006174D01*
X007620Y006015D02*
X007225Y006015D01*
X007225Y005857D02*
X007620Y005857D01*
X007620Y005698D02*
X007225Y005698D01*
X007225Y005540D02*
X007754Y005540D01*
X007225Y005064D02*
X009725Y005064D01*
X006625Y004525D02*
X006625Y003525D01*
X004625Y003525D01*
X004918Y005625D02*
X004921Y005689D01*
X004930Y005753D01*
X004944Y005816D01*
X004964Y005877D01*
X004990Y005936D01*
X005021Y005992D01*
X005057Y006046D01*
X005097Y006096D01*
X005142Y006142D01*
X005191Y006183D01*
X005244Y006221D01*
X005300Y006253D01*
X005358Y006280D01*
X005419Y006301D01*
X005481Y006317D01*
X005545Y006327D01*
X005609Y006332D01*
X005673Y006330D01*
X005737Y006323D01*
X005800Y006310D01*
X005862Y006291D01*
X005921Y006267D01*
X005978Y006237D01*
X006033Y006203D01*
X006084Y006163D01*
X006131Y006119D01*
X006173Y006071D01*
X006212Y006019D01*
X006245Y005964D01*
X006273Y005907D01*
X006296Y005847D01*
X006314Y005785D01*
X006325Y005721D01*
X006331Y005657D01*
X006331Y005593D01*
X006325Y005529D01*
X006314Y005465D01*
X006296Y005403D01*
X006273Y005343D01*
X006245Y005286D01*
X006212Y005231D01*
X006173Y005179D01*
X006131Y005131D01*
X006084Y005087D01*
X006033Y005047D01*
X005979Y005013D01*
X005921Y004983D01*
X005862Y004959D01*
X005800Y004940D01*
X005737Y004927D01*
X005673Y004920D01*
X005609Y004918D01*
X005545Y004923D01*
X005481Y004933D01*
X005419Y004949D01*
X005358Y004970D01*
X005300Y004997D01*
X005244Y005029D01*
X005191Y005067D01*
X005142Y005108D01*
X005097Y005154D01*
X005057Y005204D01*
X005021Y005258D01*
X004990Y005314D01*
X004964Y005373D01*
X004944Y005434D01*
X004930Y005497D01*
X004921Y005561D01*
X004918Y005625D01*
D13*
X001625Y005625D03*
X008225Y005925D03*
D14*
X001625Y001625D03*
D15*
X003625Y001625D03*
D16*
X009564Y002233D03*
X009564Y003217D03*
D17*
X010125Y008625D03*
D18*
X010125Y008025D03*
D19*
X005634Y001056D02*
X005634Y001196D01*
X005635Y001195D02*
X005682Y001198D01*
X005728Y001205D01*
X005773Y001215D01*
X005817Y001229D01*
X005860Y001247D01*
X005902Y001269D01*
X005941Y001294D01*
X005978Y001322D01*
X006013Y001353D01*
X006045Y001387D01*
X006074Y001424D01*
X006100Y001462D01*
X006122Y001503D01*
X006141Y001546D01*
X006156Y001590D01*
X006168Y001635D01*
X006176Y001681D01*
X006180Y001728D01*
X006179Y001774D01*
X006175Y001821D01*
X006168Y001867D01*
X006156Y001912D01*
X006140Y001956D01*
X006121Y001998D01*
X006099Y002039D01*
X006073Y002078D01*
X006044Y002114D01*
X006012Y002148D01*
X005977Y002179D01*
X005940Y002207D01*
X005900Y002232D01*
X005859Y002253D01*
X005816Y002271D01*
X005771Y002285D01*
X005726Y002296D01*
X005680Y002302D01*
X005633Y002305D01*
X005587Y002304D01*
X005540Y002299D01*
X005495Y002289D01*
X005450Y002277D01*
X005406Y002260D01*
X005364Y002240D01*
X005324Y002216D01*
X005286Y002189D01*
X005250Y002159D01*
X005217Y002126D01*
X005187Y002091D01*
X005076Y002174D01*
X005075Y002175D01*
X005109Y002216D01*
X005146Y002254D01*
X005186Y002289D01*
X005228Y002320D01*
X005273Y002349D01*
X005319Y002374D01*
X005368Y002396D01*
X005418Y002413D01*
X005469Y002427D01*
X005521Y002437D01*
X005573Y002443D01*
X005626Y002445D01*
X005679Y002443D01*
X005732Y002437D01*
X005784Y002427D01*
X005835Y002413D01*
X005885Y002395D01*
X005933Y002373D01*
X005980Y002348D01*
X006024Y002319D01*
X006066Y002287D01*
X006106Y002252D01*
X006143Y002214D01*
X006176Y002173D01*
X006207Y002130D01*
X006234Y002084D01*
X006258Y002037D01*
X006278Y001988D01*
X006294Y001937D01*
X006307Y001886D01*
X006315Y001834D01*
X006319Y001781D01*
X006320Y001728D01*
X006316Y001675D01*
X006308Y001623D01*
X006297Y001571D01*
X006281Y001520D01*
X006262Y001471D01*
X006238Y001423D01*
X006212Y001378D01*
X006182Y001334D01*
X006148Y001293D01*
X006112Y001254D01*
X006073Y001219D01*
X006031Y001186D01*
X005987Y001157D01*
X005941Y001131D01*
X005893Y001109D01*
X005843Y001090D01*
X005792Y001075D01*
X005740Y001065D01*
X005688Y001058D01*
X005635Y001055D01*
X005635Y001073D01*
X005687Y001076D01*
X005738Y001082D01*
X005788Y001093D01*
X005838Y001107D01*
X005886Y001125D01*
X005933Y001147D01*
X005978Y001172D01*
X006021Y001201D01*
X006061Y001233D01*
X006100Y001267D01*
X006135Y001305D01*
X006167Y001345D01*
X006197Y001387D01*
X006223Y001432D01*
X006245Y001478D01*
X006264Y001526D01*
X006279Y001576D01*
X006291Y001626D01*
X006298Y001677D01*
X006302Y001729D01*
X006301Y001780D01*
X006297Y001832D01*
X006289Y001882D01*
X006277Y001933D01*
X006261Y001982D01*
X006242Y002029D01*
X006219Y002076D01*
X006192Y002120D01*
X006162Y002162D01*
X006129Y002202D01*
X006093Y002239D01*
X006055Y002273D01*
X006014Y002304D01*
X005970Y002332D01*
X005925Y002357D01*
X005878Y002378D01*
X005830Y002395D01*
X005780Y002409D01*
X005729Y002419D01*
X005678Y002425D01*
X005626Y002427D01*
X005575Y002425D01*
X005524Y002419D01*
X005473Y002410D01*
X005423Y002396D01*
X005374Y002379D01*
X005327Y002358D01*
X005282Y002334D01*
X005238Y002306D01*
X005197Y002275D01*
X005159Y002241D01*
X005123Y002204D01*
X005089Y002164D01*
X005104Y002153D01*
X005137Y002193D01*
X005173Y002229D01*
X005211Y002263D01*
X005252Y002293D01*
X005295Y002321D01*
X005341Y002344D01*
X005388Y002365D01*
X005436Y002381D01*
X005486Y002394D01*
X005536Y002403D01*
X005587Y002408D01*
X005638Y002409D01*
X005689Y002406D01*
X005740Y002399D01*
X005790Y002388D01*
X005839Y002373D01*
X005887Y002355D01*
X005933Y002333D01*
X005977Y002307D01*
X006019Y002278D01*
X006059Y002246D01*
X006096Y002211D01*
X006130Y002173D01*
X006162Y002132D01*
X006190Y002090D01*
X006214Y002045D01*
X006236Y001998D01*
X006253Y001950D01*
X006267Y001901D01*
X006276Y001850D01*
X006282Y001800D01*
X006284Y001748D01*
X006282Y001697D01*
X006276Y001647D01*
X006266Y001596D01*
X006252Y001547D01*
X006234Y001499D01*
X006213Y001453D01*
X006188Y001408D01*
X006160Y001365D01*
X006128Y001325D01*
X006094Y001287D01*
X006057Y001252D01*
X006017Y001220D01*
X005974Y001191D01*
X005930Y001166D01*
X005884Y001144D01*
X005836Y001126D01*
X005787Y001111D01*
X005737Y001101D01*
X005686Y001094D01*
X005635Y001091D01*
X005635Y001109D01*
X005686Y001112D01*
X005736Y001119D01*
X005786Y001129D01*
X005834Y001144D01*
X005881Y001162D01*
X005927Y001185D01*
X005971Y001210D01*
X006012Y001239D01*
X006052Y001272D01*
X006088Y001307D01*
X006122Y001345D01*
X006152Y001385D01*
X006179Y001428D01*
X006203Y001473D01*
X006223Y001520D01*
X006240Y001568D01*
X006252Y001617D01*
X006261Y001667D01*
X006265Y001717D01*
X006266Y001768D01*
X006262Y001819D01*
X006255Y001869D01*
X006244Y001918D01*
X006228Y001967D01*
X006209Y002014D01*
X006187Y002059D01*
X006160Y002103D01*
X006131Y002144D01*
X006098Y002183D01*
X006062Y002219D01*
X006024Y002252D01*
X005983Y002282D01*
X005940Y002308D01*
X005895Y002332D01*
X005848Y002351D01*
X005800Y002367D01*
X005750Y002379D01*
X005700Y002387D01*
X005650Y002391D01*
X005599Y002390D01*
X005548Y002386D01*
X005498Y002378D01*
X005449Y002366D01*
X005401Y002350D01*
X005354Y002331D01*
X005309Y002308D01*
X005266Y002281D01*
X005225Y002251D01*
X005186Y002218D01*
X005151Y002181D01*
X005118Y002143D01*
X005133Y002132D01*
X005164Y002169D01*
X005199Y002205D01*
X005236Y002237D01*
X005276Y002266D01*
X005318Y002292D01*
X005362Y002315D01*
X005407Y002334D01*
X005454Y002349D01*
X005502Y002361D01*
X005551Y002369D01*
X005600Y002372D01*
X005649Y002373D01*
X005698Y002369D01*
X005747Y002361D01*
X005795Y002349D01*
X005842Y002334D01*
X005887Y002315D01*
X005931Y002293D01*
X005973Y002267D01*
X006013Y002238D01*
X006050Y002205D01*
X006085Y002170D01*
X006117Y002133D01*
X006145Y002093D01*
X006171Y002050D01*
X006193Y002006D01*
X006211Y001961D01*
X006226Y001914D01*
X006237Y001866D01*
X006244Y001817D01*
X006248Y001768D01*
X006247Y001718D01*
X006243Y001669D01*
X006234Y001621D01*
X006222Y001573D01*
X006206Y001526D01*
X006187Y001481D01*
X006164Y001437D01*
X006137Y001396D01*
X006108Y001356D01*
X006075Y001319D01*
X006040Y001285D01*
X006002Y001254D01*
X005961Y001226D01*
X005919Y001201D01*
X005874Y001179D01*
X005828Y001161D01*
X005781Y001147D01*
X005733Y001136D01*
X005684Y001130D01*
X005635Y001127D01*
X005635Y001145D01*
X005684Y001148D01*
X005732Y001155D01*
X005780Y001165D01*
X005826Y001180D01*
X005872Y001198D01*
X005915Y001219D01*
X005957Y001244D01*
X005997Y001273D01*
X006034Y001304D01*
X006069Y001339D01*
X006101Y001376D01*
X006129Y001416D01*
X006154Y001457D01*
X006176Y001501D01*
X006195Y001546D01*
X006209Y001593D01*
X006220Y001640D01*
X006227Y001689D01*
X006230Y001738D01*
X006229Y001786D01*
X006224Y001835D01*
X006215Y001883D01*
X006203Y001930D01*
X006186Y001976D01*
X006166Y002021D01*
X006143Y002063D01*
X006116Y002104D01*
X006085Y002142D01*
X006052Y002178D01*
X006016Y002211D01*
X005978Y002241D01*
X005937Y002268D01*
X005894Y002292D01*
X005850Y002312D01*
X005804Y002328D01*
X005757Y002341D01*
X005709Y002349D01*
X005660Y002354D01*
X005611Y002355D01*
X005562Y002352D01*
X005514Y002345D01*
X005467Y002334D01*
X005420Y002319D01*
X005375Y002301D01*
X005331Y002279D01*
X005289Y002253D01*
X005250Y002225D01*
X005213Y002193D01*
X005179Y002158D01*
X005147Y002121D01*
X005161Y002110D01*
X005193Y002147D01*
X005227Y002181D01*
X005264Y002213D01*
X005303Y002241D01*
X005344Y002266D01*
X005388Y002287D01*
X005433Y002305D01*
X005479Y002319D01*
X005526Y002329D01*
X005574Y002335D01*
X005622Y002337D01*
X005671Y002335D01*
X005719Y002329D01*
X005766Y002320D01*
X005812Y002306D01*
X005857Y002289D01*
X005901Y002268D01*
X005943Y002244D01*
X005982Y002216D01*
X006019Y002185D01*
X006054Y002151D01*
X006085Y002114D01*
X006114Y002075D01*
X006139Y002034D01*
X006160Y001991D01*
X006178Y001946D01*
X006193Y001900D01*
X006203Y001852D01*
X006209Y001805D01*
X006212Y001756D01*
X006210Y001708D01*
X006205Y001660D01*
X006196Y001613D01*
X006182Y001566D01*
X006165Y001521D01*
X006145Y001477D01*
X006121Y001435D01*
X006093Y001396D01*
X006062Y001358D01*
X006029Y001324D01*
X005992Y001292D01*
X005953Y001263D01*
X005912Y001238D01*
X005869Y001216D01*
X005824Y001198D01*
X005778Y001183D01*
X005731Y001173D01*
X005683Y001166D01*
X005635Y001163D01*
X005635Y001181D01*
X005683Y001184D01*
X005730Y001191D01*
X005777Y001202D01*
X005822Y001216D01*
X005866Y001235D01*
X005909Y001257D01*
X005949Y001282D01*
X005987Y001311D01*
X006023Y001343D01*
X006055Y001378D01*
X006085Y001415D01*
X006112Y001455D01*
X006135Y001497D01*
X006154Y001541D01*
X006170Y001586D01*
X006182Y001632D01*
X006190Y001679D01*
X006194Y001727D01*
X006193Y001775D01*
X006189Y001822D01*
X006181Y001869D01*
X006169Y001916D01*
X006153Y001961D01*
X006134Y002004D01*
X006111Y002046D01*
X006084Y002086D01*
X006054Y002123D01*
X006022Y002158D01*
X005986Y002190D01*
X005948Y002219D01*
X005907Y002244D01*
X005865Y002266D01*
X005821Y002284D01*
X005775Y002299D01*
X005728Y002310D01*
X005681Y002316D01*
X005633Y002319D01*
X005586Y002318D01*
X005538Y002312D01*
X005491Y002303D01*
X005445Y002290D01*
X005401Y002273D01*
X005357Y002252D01*
X005316Y002228D01*
X005277Y002200D01*
X005241Y002170D01*
X005207Y002136D01*
X005176Y002099D01*
M02*

17
testdata/proj1.drl vendored Normal file
View File

@ -0,0 +1,17 @@
T01 0.028in
T02 0.035in
T03 0.042in
T04 0.052in
T05 0.0595in
T06 0.086in
T07 0.125in
T08 0.152in
T09 0.025in
T10 0.032in
T11 0.1015in
T12 0.0670in
T13 0.0465in
T14 0.036in
T15 0.136in
T16 0.020in
T17 0.032in

160
testdata/proj1.plc vendored Normal file
View File

@ -0,0 +1,160 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10C,0.0050*%
%ADD11C,0.0060*%
%ADD12C,0.0260*%
%ADD13R,0.2600X0.0240*%
D10*
X003450Y002300D02*
X003450Y002750D01*
X003300Y002750D02*
X003600Y002750D01*
X003760Y002750D02*
X003985Y002750D01*
X004060Y002675D01*
X004060Y002525D01*
X003985Y002450D01*
X003760Y002450D01*
X003760Y002300D02*
X003760Y002750D01*
X004221Y002525D02*
X004521Y002525D01*
X004446Y002300D02*
X004446Y002750D01*
X004221Y002525D01*
X002521Y002500D02*
X002221Y002500D01*
X002521Y002800D01*
X002521Y002875D01*
X002446Y002950D01*
X002296Y002950D01*
X002221Y002875D01*
X002060Y002875D02*
X002060Y002725D01*
X001985Y002650D01*
X001760Y002650D01*
X001760Y002500D02*
X001760Y002950D01*
X001985Y002950D01*
X002060Y002875D01*
X001600Y002950D02*
X001300Y002950D01*
X001450Y002950D02*
X001450Y002500D01*
X001400Y006150D02*
X001400Y006600D01*
X001250Y006600D02*
X001550Y006600D01*
X001710Y006600D02*
X001935Y006600D01*
X002010Y006525D01*
X002010Y006375D01*
X001935Y006300D01*
X001710Y006300D01*
X001710Y006150D02*
X001710Y006600D01*
X002171Y006525D02*
X002246Y006600D01*
X002396Y006600D01*
X002471Y006525D01*
X002471Y006450D01*
X002396Y006375D01*
X002471Y006300D01*
X002471Y006225D01*
X002396Y006150D01*
X002246Y006150D01*
X002171Y006225D01*
X002321Y006375D02*
X002396Y006375D01*
X007850Y006900D02*
X008150Y006900D01*
X008000Y006900D02*
X008000Y006450D01*
X008310Y006450D02*
X008310Y006900D01*
X008535Y006900D01*
X008610Y006825D01*
X008610Y006675D01*
X008535Y006600D01*
X008310Y006600D01*
X008771Y006450D02*
X009071Y006450D01*
X008921Y006450D02*
X008921Y006900D01*
X008771Y006750D01*
X009039Y004468D02*
X009039Y004167D01*
X008889Y004007D02*
X008589Y004007D01*
X008739Y004167D02*
X008589Y004317D01*
X009039Y004317D01*
X008889Y004007D02*
X009039Y003857D01*
X008889Y003707D01*
X008589Y003707D01*
X008664Y003547D02*
X008589Y003472D01*
X008589Y003322D01*
X008664Y003247D01*
X008739Y003247D01*
X008814Y003322D01*
X008814Y003472D01*
X008889Y003547D01*
X008964Y003547D01*
X009039Y003472D01*
X009039Y003322D01*
X008964Y003247D01*
X010289Y003848D02*
X010589Y004148D01*
X010589Y004223D01*
X010514Y004298D01*
X010364Y004298D01*
X010289Y004223D01*
X010289Y003848D02*
X010589Y003848D01*
X010489Y001478D02*
X010489Y001028D01*
X010339Y001028D02*
X010639Y001028D01*
X010339Y001328D02*
X010489Y001478D01*
D11*
X010664Y001773D02*
X010314Y001773D01*
X010314Y003643D01*
X010664Y003643D01*
X014314Y003643D01*
X014564Y003643D01*
X014914Y003843D01*
X014914Y004643D01*
X009714Y004643D01*
X009714Y003603D01*
X010664Y003643D02*
X010664Y001773D01*
X014314Y001773D01*
X014314Y003643D01*
X014314Y001773D02*
X014564Y001773D01*
X014914Y001573D01*
X014914Y000783D01*
X009714Y000783D01*
X009714Y001843D01*
X009714Y002623D02*
X009714Y002823D01*
D12*
X013214Y003217D02*
X013414Y003217D01*
X013414Y002233D02*
X013214Y002233D01*
D13*
X011964Y002233D03*
X011964Y003213D03*
M02*

43
testdata/proj1.pls vendored Normal file
View File

@ -0,0 +1,43 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10C,0.0040*%
D10*
X003138Y007845D02*
X003445Y007845D01*
X003598Y007845D02*
X003751Y007998D01*
X003675Y007998D02*
X003905Y007998D01*
X003675Y007998D02*
X003598Y008075D01*
X003598Y008229D01*
X003675Y008305D01*
X003905Y008305D01*
X003905Y007845D01*
X003445Y008152D02*
X003291Y008305D01*
X003291Y007845D01*
X005588Y008195D02*
X005895Y008195D01*
X006048Y008195D02*
X006201Y008348D01*
X006048Y008272D02*
X006125Y008195D01*
X006278Y008195D01*
X006355Y008272D01*
X006355Y008579D01*
X006278Y008655D01*
X006125Y008655D01*
X006048Y008579D01*
X006048Y008272D01*
X005741Y008195D02*
X005741Y008655D01*
X005895Y008502D01*
M02*

BIN
testdata/proj1.sch vendored Normal file

Binary file not shown.

652
testdata/proj1.sol vendored Normal file
View File

@ -0,0 +1,652 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10OC8,0.0850*%
%ADD11O,0.0780X0.1560*%
%ADD12O,0.1560X0.0780*%
%ADD13R,0.0394X0.0551*%
%ADD14R,0.0630X0.0787*%
%ADD15O,0.1200X0.0600*%
%ADD16C,0.0400*%
%ADD17C,0.0100*%
%ADD18R,0.0400X0.0400*%
D10*
X001625Y005625D03*
X008225Y005925D03*
D11*
X001625Y001625D03*
D12*
X003625Y001625D03*
D13*
X005251Y006992D03*
X005999Y006992D03*
X005625Y007858D03*
D14*
X003976Y007325D03*
X002874Y007325D03*
D15*
X009564Y003217D03*
X009564Y002233D03*
D16*
X010125Y008625D03*
D17*
X002165Y001011D02*
X002080Y000927D01*
X010760Y000927D01*
X010760Y009423D01*
X001190Y009423D01*
X001190Y006003D01*
X001387Y006200D01*
X001863Y006200D01*
X002200Y005863D01*
X002200Y005387D01*
X001863Y005050D01*
X001387Y005050D01*
X001190Y005247D01*
X001190Y002343D01*
X001401Y002555D01*
X001849Y002555D01*
X002165Y002239D01*
X002165Y001011D01*
X002165Y001019D02*
X010760Y001019D01*
X010760Y001118D02*
X004271Y001118D01*
X004239Y001085D02*
X004555Y001401D01*
X004555Y001849D01*
X004239Y002165D01*
X003011Y002165D01*
X002695Y001849D01*
X002695Y001401D01*
X003011Y001085D01*
X004239Y001085D01*
X004370Y001216D02*
X010760Y001216D01*
X010760Y001315D02*
X004468Y001315D01*
X004555Y001413D02*
X010760Y001413D01*
X010760Y001512D02*
X004555Y001512D01*
X004555Y001610D02*
X010760Y001610D01*
X010760Y001709D02*
X004555Y001709D01*
X004555Y001807D02*
X009053Y001807D01*
X009078Y001783D02*
X010051Y001783D01*
X010314Y002046D01*
X010314Y002419D01*
X010051Y002683D01*
X009078Y002683D01*
X008814Y002419D01*
X008814Y002046D01*
X009078Y001783D01*
X008955Y001906D02*
X004497Y001906D01*
X004399Y002004D02*
X008856Y002004D01*
X008814Y002103D02*
X004300Y002103D01*
X002949Y002103D02*
X002165Y002103D01*
X002165Y002004D02*
X002851Y002004D01*
X002752Y001906D02*
X002165Y001906D01*
X002165Y001807D02*
X002695Y001807D01*
X002695Y001709D02*
X002165Y001709D01*
X002165Y001610D02*
X002695Y001610D01*
X002695Y001512D02*
X002165Y001512D01*
X002165Y001413D02*
X002695Y001413D01*
X002781Y001315D02*
X002165Y001315D01*
X002165Y001216D02*
X002880Y001216D01*
X002979Y001118D02*
X002165Y001118D01*
X002165Y002202D02*
X008814Y002202D01*
X008814Y002300D02*
X002103Y002300D01*
X002005Y002399D02*
X008814Y002399D01*
X008892Y002497D02*
X001906Y002497D01*
X001344Y002497D02*
X001190Y002497D01*
X001190Y002399D02*
X001245Y002399D01*
X001190Y002596D02*
X008991Y002596D01*
X009078Y002767D02*
X010051Y002767D01*
X010314Y003031D01*
X010314Y003403D01*
X010051Y003667D01*
X009078Y003667D01*
X008814Y003403D01*
X008814Y003031D01*
X009078Y002767D01*
X009052Y002793D02*
X001190Y002793D01*
X001190Y002891D02*
X008953Y002891D01*
X008855Y002990D02*
X001190Y002990D01*
X001190Y003088D02*
X008814Y003088D01*
X008814Y003187D02*
X001190Y003187D01*
X001190Y003286D02*
X008814Y003286D01*
X008814Y003384D02*
X001190Y003384D01*
X001190Y003483D02*
X008894Y003483D01*
X008992Y003581D02*
X001190Y003581D01*
X001190Y003680D02*
X010760Y003680D01*
X010760Y003778D02*
X001190Y003778D01*
X001190Y003877D02*
X010760Y003877D01*
X010760Y003975D02*
X001190Y003975D01*
X001190Y004074D02*
X010760Y004074D01*
X010760Y004172D02*
X001190Y004172D01*
X001190Y004271D02*
X010760Y004271D01*
X010760Y004370D02*
X001190Y004370D01*
X001190Y004468D02*
X010760Y004468D01*
X010760Y004567D02*
X001190Y004567D01*
X001190Y004665D02*
X010760Y004665D01*
X010760Y004764D02*
X001190Y004764D01*
X001190Y004862D02*
X010760Y004862D01*
X010760Y004961D02*
X001190Y004961D01*
X001190Y005059D02*
X001377Y005059D01*
X001279Y005158D02*
X001190Y005158D01*
X001872Y005059D02*
X005501Y005059D01*
X005511Y005055D02*
X005738Y005055D01*
X005948Y005142D01*
X006108Y005302D01*
X006195Y005511D01*
X006195Y005738D01*
X006108Y005948D01*
X005948Y006108D01*
X005738Y006195D01*
X005511Y006195D01*
X005302Y006108D01*
X005142Y005948D01*
X005055Y005738D01*
X005055Y005511D01*
X005142Y005302D01*
X005302Y005142D01*
X005511Y005055D01*
X005286Y005158D02*
X001971Y005158D01*
X002070Y005256D02*
X005187Y005256D01*
X005120Y005355D02*
X002168Y005355D01*
X002200Y005454D02*
X005079Y005454D01*
X005055Y005552D02*
X002200Y005552D01*
X002200Y005651D02*
X005055Y005651D01*
X005059Y005749D02*
X002200Y005749D01*
X002200Y005848D02*
X005100Y005848D01*
X005141Y005946D02*
X002117Y005946D01*
X002018Y006045D02*
X005239Y006045D01*
X005387Y006143D02*
X001920Y006143D01*
X001330Y006143D02*
X001190Y006143D01*
X001190Y006045D02*
X001232Y006045D01*
X001190Y006242D02*
X003325Y006242D01*
X003342Y006225D02*
X005542Y006225D01*
X005708Y006225D01*
X006049Y006566D01*
X006258Y006566D01*
X006346Y006654D01*
X006346Y007330D01*
X006258Y007417D01*
X005740Y007417D01*
X005652Y007330D01*
X005652Y006735D01*
X005598Y006680D01*
X005598Y007330D01*
X005510Y007417D01*
X004992Y007417D01*
X004904Y007330D01*
X004904Y006654D01*
X004933Y006625D01*
X003508Y006625D01*
X003301Y006831D01*
X003339Y006869D01*
X003339Y007781D01*
X003251Y007869D01*
X002497Y007869D01*
X002409Y007781D01*
X002409Y006869D01*
X002497Y006781D01*
X002786Y006781D01*
X003225Y006342D01*
X003342Y006225D01*
X003227Y006340D02*
X001190Y006340D01*
X001190Y006439D02*
X003128Y006439D01*
X003029Y006537D02*
X001190Y006537D01*
X001190Y006636D02*
X002931Y006636D01*
X002832Y006735D02*
X001190Y006735D01*
X001190Y006833D02*
X002445Y006833D01*
X002409Y006932D02*
X001190Y006932D01*
X001190Y007030D02*
X002409Y007030D01*
X002409Y007129D02*
X001190Y007129D01*
X001190Y007227D02*
X002409Y007227D01*
X002409Y007326D02*
X001190Y007326D01*
X001190Y007424D02*
X002409Y007424D01*
X002409Y007523D02*
X001190Y007523D01*
X001190Y007621D02*
X002409Y007621D01*
X002409Y007720D02*
X001190Y007720D01*
X001190Y007819D02*
X002447Y007819D01*
X002874Y007325D02*
X002874Y006976D01*
X003425Y006425D01*
X005625Y006425D01*
X006025Y006825D01*
X005999Y006851D01*
X005999Y006992D01*
X005652Y007030D02*
X005598Y007030D01*
X005598Y006932D02*
X005652Y006932D01*
X005652Y006833D02*
X005598Y006833D01*
X005598Y006735D02*
X005652Y006735D01*
X006020Y006537D02*
X010760Y006537D01*
X010760Y006439D02*
X008524Y006439D01*
X008463Y006500D02*
X008275Y006500D01*
X008275Y005975D01*
X008800Y005975D01*
X008800Y006163D01*
X008463Y006500D01*
X008275Y006439D02*
X008175Y006439D01*
X008175Y006500D02*
X007987Y006500D01*
X007650Y006163D01*
X007650Y005975D01*
X008175Y005975D01*
X008175Y006500D01*
X008175Y006340D02*
X008275Y006340D01*
X008275Y006242D02*
X008175Y006242D01*
X008175Y006143D02*
X008275Y006143D01*
X008275Y006045D02*
X008175Y006045D01*
X008175Y005975D02*
X008275Y005975D01*
X008275Y005875D01*
X008800Y005875D01*
X008800Y005687D01*
X008463Y005350D01*
X008275Y005350D01*
X008275Y005875D01*
X008175Y005875D01*
X008175Y005350D01*
X007987Y005350D01*
X007650Y005687D01*
X007650Y005875D01*
X008175Y005875D01*
X008175Y005975D01*
X008175Y005946D02*
X006109Y005946D01*
X006150Y005848D02*
X007650Y005848D01*
X007650Y005749D02*
X006190Y005749D01*
X006195Y005651D02*
X007686Y005651D01*
X007785Y005552D02*
X006195Y005552D01*
X006171Y005454D02*
X007883Y005454D01*
X007982Y005355D02*
X006130Y005355D01*
X006063Y005256D02*
X010760Y005256D01*
X010760Y005158D02*
X005964Y005158D01*
X005749Y005059D02*
X010760Y005059D01*
X010760Y005355D02*
X008468Y005355D01*
X008567Y005454D02*
X010760Y005454D01*
X010760Y005552D02*
X008665Y005552D01*
X008764Y005651D02*
X010760Y005651D01*
X010760Y005749D02*
X008800Y005749D01*
X008800Y005848D02*
X010760Y005848D01*
X010760Y005946D02*
X008275Y005946D01*
X008275Y005848D02*
X008175Y005848D01*
X008175Y005749D02*
X008275Y005749D01*
X008275Y005651D02*
X008175Y005651D01*
X008175Y005552D02*
X008275Y005552D01*
X008275Y005454D02*
X008175Y005454D01*
X008175Y005355D02*
X008275Y005355D01*
X008800Y006045D02*
X010760Y006045D01*
X010760Y006143D02*
X008800Y006143D01*
X008721Y006242D02*
X010760Y006242D01*
X010760Y006340D02*
X008622Y006340D01*
X007926Y006439D02*
X005922Y006439D01*
X005823Y006340D02*
X007827Y006340D01*
X007729Y006242D02*
X005725Y006242D01*
X005863Y006143D02*
X007650Y006143D01*
X007650Y006045D02*
X006011Y006045D01*
X006328Y006636D02*
X010760Y006636D01*
X010760Y006735D02*
X006346Y006735D01*
X006346Y006833D02*
X010760Y006833D01*
X010760Y006932D02*
X006346Y006932D01*
X006346Y007030D02*
X010760Y007030D01*
X010760Y007129D02*
X006346Y007129D01*
X006346Y007227D02*
X010760Y007227D01*
X010760Y007326D02*
X006346Y007326D01*
X005972Y007520D02*
X005884Y007432D01*
X005366Y007432D01*
X005278Y007520D01*
X005278Y007658D01*
X004441Y007658D01*
X004441Y006869D01*
X004353Y006781D01*
X003599Y006781D01*
X003511Y006869D01*
X003511Y007781D01*
X003599Y007869D01*
X003825Y007869D01*
X003825Y007941D01*
X003942Y008058D01*
X004108Y008058D01*
X005278Y008058D01*
X005278Y008196D01*
X005366Y008284D01*
X005884Y008284D01*
X005972Y008196D01*
X005972Y007520D01*
X005972Y007523D02*
X010760Y007523D01*
X010760Y007621D02*
X005972Y007621D01*
X005972Y007720D02*
X009818Y007720D01*
X009775Y007763D02*
X009863Y007675D01*
X010387Y007675D01*
X010475Y007763D01*
X010475Y008287D01*
X010387Y008375D01*
X010370Y008375D01*
X010422Y008427D01*
X010475Y008555D01*
X010475Y008694D01*
X010422Y008823D01*
X010323Y008922D01*
X010194Y008975D01*
X010055Y008975D01*
X009927Y008922D01*
X009828Y008823D01*
X009775Y008694D01*
X009775Y008555D01*
X009828Y008427D01*
X009880Y008375D01*
X009863Y008375D01*
X009775Y008287D01*
X009775Y007763D01*
X009775Y007819D02*
X005972Y007819D01*
X005972Y007917D02*
X009775Y007917D01*
X009775Y008016D02*
X005972Y008016D01*
X005972Y008114D02*
X009775Y008114D01*
X009775Y008213D02*
X005955Y008213D01*
X005625Y007858D02*
X004025Y007858D01*
X004025Y007325D01*
X003976Y007325D01*
X003511Y007326D02*
X003339Y007326D01*
X003339Y007424D02*
X003511Y007424D01*
X003511Y007523D02*
X003339Y007523D01*
X003339Y007621D02*
X003511Y007621D01*
X003511Y007720D02*
X003339Y007720D01*
X003301Y007819D02*
X003549Y007819D01*
X003825Y007917D02*
X001190Y007917D01*
X001190Y008016D02*
X003900Y008016D01*
X004441Y007621D02*
X005278Y007621D01*
X005278Y007523D02*
X004441Y007523D01*
X004441Y007424D02*
X010760Y007424D01*
X010760Y007720D02*
X010432Y007720D01*
X010475Y007819D02*
X010760Y007819D01*
X010760Y007917D02*
X010475Y007917D01*
X010475Y008016D02*
X010760Y008016D01*
X010760Y008114D02*
X010475Y008114D01*
X010475Y008213D02*
X010760Y008213D01*
X010760Y008311D02*
X010451Y008311D01*
X010405Y008410D02*
X010760Y008410D01*
X010760Y008508D02*
X010455Y008508D01*
X010475Y008607D02*
X010760Y008607D01*
X010760Y008705D02*
X010470Y008705D01*
X010430Y008804D02*
X010760Y008804D01*
X010760Y008903D02*
X010342Y008903D01*
X010760Y009001D02*
X001190Y009001D01*
X001190Y008903D02*
X009908Y008903D01*
X009820Y008804D02*
X001190Y008804D01*
X001190Y008705D02*
X009779Y008705D01*
X009775Y008607D02*
X001190Y008607D01*
X001190Y008508D02*
X009794Y008508D01*
X009845Y008410D02*
X001190Y008410D01*
X001190Y008311D02*
X009799Y008311D01*
X010760Y009100D02*
X001190Y009100D01*
X001190Y009198D02*
X010760Y009198D01*
X010760Y009297D02*
X001190Y009297D01*
X001190Y009395D02*
X010760Y009395D01*
X005652Y007326D02*
X005598Y007326D01*
X005598Y007227D02*
X005652Y007227D01*
X005652Y007129D02*
X005598Y007129D01*
X004904Y007129D02*
X004441Y007129D01*
X004441Y007227D02*
X004904Y007227D01*
X004904Y007326D02*
X004441Y007326D01*
X004441Y007030D02*
X004904Y007030D01*
X004904Y006932D02*
X004441Y006932D01*
X004405Y006833D02*
X004904Y006833D01*
X004904Y006735D02*
X003398Y006735D01*
X003303Y006833D02*
X003547Y006833D01*
X003511Y006932D02*
X003339Y006932D01*
X003339Y007030D02*
X003511Y007030D01*
X003511Y007129D02*
X003339Y007129D01*
X003339Y007227D02*
X003511Y007227D01*
X003496Y006636D02*
X004922Y006636D01*
X005278Y008114D02*
X001190Y008114D01*
X001190Y008213D02*
X005295Y008213D01*
X010136Y003581D02*
X010760Y003581D01*
X010760Y003483D02*
X010235Y003483D01*
X010314Y003384D02*
X010760Y003384D01*
X010760Y003286D02*
X010314Y003286D01*
X010314Y003187D02*
X010760Y003187D01*
X010760Y003088D02*
X010314Y003088D01*
X010274Y002990D02*
X010760Y002990D01*
X010760Y002891D02*
X010175Y002891D01*
X010076Y002793D02*
X010760Y002793D01*
X010760Y002694D02*
X001190Y002694D01*
X010075Y001807D02*
X010760Y001807D01*
X010760Y001906D02*
X010174Y001906D01*
X010272Y002004D02*
X010760Y002004D01*
X010760Y002103D02*
X010314Y002103D01*
X010314Y002202D02*
X010760Y002202D01*
X010760Y002300D02*
X010314Y002300D01*
X010314Y002399D02*
X010760Y002399D01*
X010760Y002497D02*
X010236Y002497D01*
X010138Y002596D02*
X010760Y002596D01*
D18*
X010125Y008025D03*
M02*

33
testdata/proj1.stc vendored Normal file
View File

@ -0,0 +1,33 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10C,0.0320*%
%ADD11OC8,0.0930*%
%ADD12O,0.0860X0.1640*%
%ADD13O,0.1640X0.0860*%
%ADD14O,0.1280X0.0680*%
%ADD15C,0.0480*%
%ADD16R,0.0480X0.0480*%
D10*
X005625Y005625D03*
D11*
X008225Y005925D03*
X001625Y005625D03*
D12*
X001625Y001625D03*
D13*
X003625Y001625D03*
D14*
X009564Y002233D03*
X009564Y003217D03*
D15*
X010125Y008625D03*
D16*
X010125Y008025D03*
M02*

42
testdata/proj1.sts vendored Normal file
View File

@ -0,0 +1,42 @@
G75*
G70*
%OFA0B0*%
%FSLAX24Y24*%
%IPPOS*%
%LPD*%
%AMOC8*
5,1,8,0,0,1.08239X$1,22.5*
%
%ADD10C,0.0320*%
%ADD11OC8,0.0930*%
%ADD12O,0.0860X0.1640*%
%ADD13O,0.1640X0.0860*%
%ADD14R,0.0474X0.0631*%
%ADD15R,0.0710X0.0867*%
%ADD16O,0.1280X0.0680*%
%ADD17C,0.0480*%
%ADD18R,0.0480X0.0480*%
D10*
X005625Y005625D03*
D11*
X008225Y005925D03*
X001625Y005625D03*
D12*
X001625Y001625D03*
D13*
X003625Y001625D03*
D14*
X005251Y006992D03*
X005999Y006992D03*
X005625Y007858D03*
D15*
X003976Y007325D03*
X002874Y007325D03*
D16*
X009564Y003217D03*
X009564Y002233D03*
D17*
X010125Y008625D03*
D18*
X010125Y008025D03*
M02*

20
testdata/proj1.xln vendored Normal file
View File

@ -0,0 +1,20 @@
%
M48
M72
T01C0.0240
T02C0.0400
T03C0.0520
%
T01
X5625Y5625
X10125Y8025
X10125Y8625
T02
X9564Y3217
X9564Y2233
T03
X1625Y1625
X3625Y1625
X1625Y5625
X8225Y5925
M30