3 * Bill Wilson billw@wt.net
5 * This program is free software which I release under the GNU General Public
6 * License. You may redistribute and/or modify this program under the terms
7 * of that license as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details. Version 2 is in the
14 * COPYRIGHT file in the top level directory of this distribution.
16 * To get a copy of the GNU General Puplic License, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
34 #define GSC2PCB_VERSION "1.6"
36 #define DEFAULT_PCB_INC "pcb.inc"
38 #define SEP_STRING "--------\n"
42 gchar
*refdes
, *value
, *description
, *changed_description
, *changed_value
;
48 gboolean still_exists
, new_format
, hi_res_format
, quoted_flags
, omit_PKG
;
55 gchar
*part_number
, *element_name
;
59 static GList
*pcb_element_list
,
60 *element_directory_list
, *extra_gnetlist_list
, *extra_gnetlist_arg_list
;
62 static gchar
*sch_basename
;
64 static GList
*schematics
;
66 static gchar
*m4_pcbdir
, *default_m4_pcbdir
, *m4_files
, *m4_override_file
;
68 static gboolean use_m4
= TRUE
;
70 static gchar
*empty_footprint_name
;
79 n_preserved
, n_changed_value
, n_not_found
, n_unknown
, n_none
, n_empty
;
81 static gboolean remove_unfound_elements
= TRUE
,
83 force_element_files
, preserve
, fix_elements
, bak_done
, need_PKG_purge
;
87 create_m4_override_file ()
91 m4_override_file
= "gnet-gsch2pcb-tmp.scm";
92 f
= fopen (m4_override_file
, "wb");
94 m4_override_file
= NULL
;
98 fprintf (f
, "(define gsch2pcb:pcb-m4-dir \"%s\")\n", m4_pcbdir
);
100 fprintf (f
, "(define gsch2pcb:m4-files \"%s\")\n", m4_files
);
101 fprintf (f
, "(define gsch2pcb:use-m4 %s)\n", use_m4
== TRUE
? "#t" : "#f");
105 printf ("Default m4-pcbdir: %s\n", default_m4_pcbdir
);
106 printf ("--------\ngnet-gsch2pcb-tmp.scm override file:\n");
108 printf (" (define gsch2pcb:pcb-m4-dir \"%s\")\n", m4_pcbdir
);
110 printf (" (define gsch2pcb:m4-files \"%s\")\n", m4_files
);
111 printf (" (define gsch2pcb:use-m4 %s)\n", use_m4
== TRUE
? "#t" : "#f");
116 * Build and run a command. No redirection or error handling is
117 * done. Format string is split on whitespace. Specifiers %l and %s
118 * are replaced with contents of positional args. To be recognized,
119 * specifiers must be separated from other arguments in the format by
121 * - %l expects a GList, contents used as separate arguments
122 * - %s expects a gchar*, contents used as a single argument
123 * @param[in] format used to specify command to be executed
124 * @param[in] ... positional parameters
127 build_and_run_command (const gchar
*format
, ...)
135 gboolean result
= FALSE
;
136 gboolean spawn_result
;
137 gchar
*standard_output
= NULL
;
138 gchar
*standard_error
= NULL
;
139 GError
* error
= NULL
;
141 va_start (vargs
, format
);
142 split
= g_strsplit_set (format
, " \t\n\v", 0);
143 num_split
= g_strv_length (split
);
144 for (i
= 0; i
< num_split
; ++i
) {
145 gchar
*chunk
= split
[i
];
146 if (strcmp (chunk
, "%l") == 0) {
147 /* append contents of list into command args - shared data */
148 tmp
= g_list_concat (tmp
, g_list_copy (va_arg (vargs
, GList
*)));
149 } else if (strcmp (chunk
, "%s") == 0) {
150 /* insert contents of string into output */
151 tmp
= g_list_append (tmp
, va_arg (vargs
, gchar
*));
153 /* bare string, use as is */
154 tmp
= g_list_append (tmp
, chunk
);
160 /* we have something in the list, build & call command */
163 gchar
** args
= g_new0 (gchar
*, g_list_length (tmp
) + 1/* NULL terminate the list */);
166 printf ("Running command:\n\t");
168 for (p
= tmp
; p
; p
= g_list_next (p
)) {
169 args
[i
++] = (gchar
*) p
->data
;
171 printf ("%s ", (char*)p
->data
);
175 printf ("\n%s", SEP_STRING
);
177 if (g_spawn_sync (".", /* Working directory */
180 G_SPAWN_SEARCH_PATH
, /* flags */
181 NULL
, /* child_setup */
182 NULL
, /* user data */
183 &standard_output
, /* standard output */
184 &standard_error
, /* standard error */
185 &status
, /* exit status return */
186 &error
)) { /* GError return */
188 fputs(standard_output
, stdout
);
193 fputs(standard_error
, stderr
);
197 fprintf(stderr
, "Failed to execute external program: %s\n", error
->message
);
202 printf ("\n%s", SEP_STRING
);
204 g_free(standard_error
);
205 g_free (standard_output
);
208 /* free the list, but leave data untouched */
217 /* Run gnetlist to generate a netlist and a PCB board file. gnetlist
218 * has exit status of 0 even if it's given an invalid arg, so do some
219 * stat() hoops to decide if gnetlist successfully generated the PCB
220 * board file (only gnetlist >= 20030901 recognizes -m).
223 run_gnetlist (gchar
* pins_file
, gchar
* net_file
, gchar
* pcb_file
,
224 gchar
* basename
, GList
* largs
)
228 static const gchar
*gnetlist
= NULL
;
230 GList
*verboseList
= NULL
;
233 /* Allow the user to specify a full path or a different name for
234 * the gnetlist command. Especially useful if multiple copies
235 * are installed at once.
237 if (gnetlist
== NULL
)
238 gnetlist
= g_getenv ("GNETLIST");
239 if (gnetlist
== NULL
)
240 gnetlist
= "gnetlist";
243 verboseList
= g_list_append (verboseList
, "-q");
245 if (!build_and_run_command ("%s %l -g pcbpins -o %s %l %l",
249 extra_gnetlist_arg_list
,
253 if (!build_and_run_command ("%s %l -g PCB -o %s %l %l",
257 extra_gnetlist_arg_list
,
260 create_m4_override_file ();
262 if (m4_override_file
) {
263 args1
= g_list_append (args1
, "-m");
264 args1
= g_list_append (args1
, m4_override_file
);
267 mtime
= (stat (pcb_file
, &st
) == 0) ? st
.st_mtime
: 0;
269 if (!build_and_run_command ("%s %l -g gsch2pcb -o %s %l %l %l",
274 extra_gnetlist_arg_list
,
276 if (stat (pcb_file
, &st
) != 0 || mtime
== st
.st_mtime
) {
278 "gsch2pcb: gnetlist command failed, `%s' not updated\n",
281 if (m4_override_file
)
283 " At least gnetlist 20030901 is required for m4-xxx options.\n");
289 if (m4_override_file
)
290 unlink (m4_override_file
);
292 for (list
= extra_gnetlist_list
; list
; list
= g_list_next (list
)) {
293 const gchar
*s
= (gchar
*) list
->data
;
294 const gchar
*s2
= strstr (s
, " -o ");
298 out_file
= g_strconcat (basename
, ".", s
, NULL
);
299 backend
= g_strdup (s
);
301 out_file
= g_strdup (s2
+ 4);
302 backend
= g_strndup (s
, s2
- s
);
305 if (!build_and_run_command ("%s %l -g %s -o %s %l %l",
310 extra_gnetlist_arg_list
,
318 g_list_free (verboseList
);
324 token (gchar
* string
, gchar
** next
, gboolean
* quoted_ret
)
328 gboolean quoted
= FALSE
;
335 return g_strdup ("");
337 while (*str
== ' ' || *str
== '\t' || *str
== ',' || *str
== '\n')
344 for (s
= str
; *s
&& *s
!= '"' && *s
!= '\n'; ++s
);
349 *s
&& (*s
!= ' ' && *s
!= '\t' && *s
!= ',' && *s
!= '\n'); ++s
);
351 ret
= g_strndup (str
, s
- str
);
352 str
= (quoted
&& *s
) ? s
+ 1 : s
;
359 fix_spaces (gchar
* str
)
365 for (s
= str
; *s
; ++s
)
366 if (*s
== ' ' || *s
== '\t')
371 /* As of 1/9/2004 CVS hi_res Element[] line format:
372 * Element[element_flags, description, pcb-name, value, mark_x, mark_y,
373 * text_x, text_y, text_direction, text_scale, text_flags]
374 * New PCB 1.7 / 1.99 Element() line format:
375 * Element(element_flags, description, pcb-name, value, mark_x, mark_y,
376 * text_x, text_y, text_direction, text_scale, text_flags)
377 * Old PCB 1.6 Element() line format:
378 * Element(element_flags, description, pcb-name, value,
379 * text_x, text_y, text_direction, text_scale, text_flags)
381 * (mark_x, mark_y) is the element position (mark) and (text_x,text_y)
382 * is the description text position which is absolute in pre 1.7 and
383 * is now relative. The hi_res mark_x,mark_y and text_x,text_y resolutions
384 * are 100x the other formats.
387 pcb_element_line_parse (gchar
* line
)
389 PcbElement
*el
= NULL
;
390 gchar
*s
, *t
, close_char
;
391 gint state
= 0, elcount
= 0;
393 if (strncmp (line
, "Element", 7))
396 el
= g_new0 (PcbElement
, 1);
399 while (*s
== ' ' || *s
== '\t')
403 el
->hi_res_format
= TRUE
;
404 else if (*s
!= '(') {
409 el
->res_char
= el
->hi_res_format
? '[' : '(';
410 close_char
= el
->hi_res_format
? ']' : ')';
412 el
->flags
= token (s
+ 1, NULL
, &el
->quoted_flags
);
413 el
->description
= token (NULL
, NULL
, NULL
);
414 el
->refdes
= token (NULL
, NULL
, NULL
);
415 el
->value
= token (NULL
, NULL
, NULL
);
417 el
->x
= token (NULL
, NULL
, NULL
);
418 el
->y
= token (NULL
, &t
, NULL
);
420 el
->tail
= g_strdup (t
? t
: "");
421 if ((s
= strrchr (el
->tail
, (gint
) '\n')) != NULL
)
424 /* Count the tokens in tail to decide if it's new or old format.
425 * Old format will have 3 tokens, new format will have 5 tokens.
427 for (s
= el
->tail
; *s
&& *s
!= close_char
; ++s
) {
436 el
->new_format
= TRUE
;
438 fix_spaces (el
->description
);
439 fix_spaces (el
->refdes
);
440 fix_spaces (el
->value
);
442 /* Don't allow elements with no refdes to ever be deleted because
443 * they may be desired pc board elements not in schematics. So
444 * initialize still_exists to TRUE if empty or non-alphanumeric
447 if (!*el
->refdes
|| !isalnum ((gint
) (*el
->refdes
)))
448 el
->still_exists
= TRUE
;
454 pcb_element_free (PcbElement
* el
)
459 g_free (el
->description
);
460 g_free (el
->changed_description
);
461 g_free (el
->changed_value
);
467 g_free (el
->pkg_name_fix
);
472 get_pcb_element_list (gchar
* pcb_file
)
478 if ((f
= fopen (pcb_file
, "r")) == NULL
)
480 while ((fgets (buf
, sizeof (buf
), f
)) != NULL
) {
481 for (s
= buf
; *s
== ' ' || *s
== '\t'; ++s
);
482 if (!strncmp (s
, "PKG_", 4)) {
483 need_PKG_purge
= TRUE
;
486 if ((el
= pcb_element_line_parse (s
)) == NULL
)
488 pcb_element_list
= g_list_append (pcb_element_list
, el
);
494 pcb_element_exists (PcbElement
* el_test
, gboolean record
)
499 for (list
= pcb_element_list
; list
; list
= g_list_next (list
)) {
500 el
= (PcbElement
*) list
->data
;
502 if (strcmp (el_test
->refdes
, el
->refdes
))
504 if (strcmp (el_test
->description
, el
->description
)) { /* footprint */
506 el
->changed_description
= g_strdup (el_test
->description
);
509 if (strcmp (el_test
->value
, el
->value
))
510 el
->changed_value
= g_strdup (el_test
->value
);
511 el
->still_exists
= TRUE
;
519 /* A problem is that new PCB 1.7 file elements have the
520 * (mark_x,mark_y) value set to wherever the element was created and
521 * no equivalent of a gschem translate symbol was done.
523 * So, file elements inserted can be scattered over a big area and
524 * this is bad when loading a file.new.pcb into an existing PC
525 * board. So, do a simple translate if (mark_x,mark_y) is
526 * (arbitrarily) over 1000. I'll assume that for values < 1000 the
527 * element creator was concerned with a sane initial element
528 * placement. Unless someone has a better idea? Don't bother with
529 * pre PCB 1.7 formats as that would require parsing the mark().
530 * Current m4 elements use the old format but they seem to have a
531 * reasonable initial mark().
534 simple_translate (PcbElement
* el
)
542 insert_element (FILE * f_out
, gchar
* element_file
,
543 gchar
* footprint
, gchar
* refdes
, gchar
* value
)
547 gchar
*fmt
, *s
, buf
[1024];
548 gboolean retval
= FALSE
;
550 if ((f_in
= fopen (element_file
, "r")) == NULL
) {
551 s
= g_strdup_printf ("insert_element() can't open %s", element_file
);
556 /* Scan the file to detect whether it's actually a PCB
557 * layout. Assumes that a PCB layout will have a "PCB" line. */
558 while ((fgets (buf
, sizeof (buf
), f_in
)) != NULL
) {
559 for (s
= buf
; *s
== ' ' || *s
== '\t'; ++s
);
560 s
[3] = 0; /* Truncate line */
561 if (strncmp ("PCB", s
, sizeof (buf
)) == 0) {
562 printf ("Warning: %s appears to be a PCB layout file. Skipping.\n",
570 /* Copy the file element lines. Substitute new parameters into the
571 * Element() or Element[] line and strip comments.
573 while ((fgets (buf
, sizeof (buf
), f_in
)) != NULL
) {
574 for (s
= buf
; *s
== ' ' || *s
== '\t'; ++s
);
575 if ((el
= pcb_element_line_parse (s
)) != NULL
) {
576 simple_translate (el
);
577 fmt
= el
->quoted_flags
?
578 "Element%c\"%s\" \"%s\" \"%s\" \"%s\" %s %s%s\n" :
579 "Element%c%s \"%s\" \"%s\" \"%s\" %s %s%s\n";
582 el
->res_char
, el
->flags
, footprint
, refdes
, value
,
583 el
->x
, el
->y
, el
->tail
);
585 } else if (*s
!= '#')
587 pcb_element_free (el
);
595 find_element (gchar
* dir_path
, gchar
* element
)
598 gchar
*path
, *name
, *s
, *found
= NULL
;
600 if ((dir
= g_dir_open (dir_path
, 0, NULL
)) == NULL
) {
601 s
= g_strdup_printf ("find_element can't open dir \"%s\"", dir_path
);
607 printf ("\t Searching: \"%s\" for \"%s\"\n", dir_path
, element
);
608 while ((name
= (gchar
*) g_dir_read_name (dir
)) != NULL
) {
609 path
= g_strconcat (dir_path
, "/", name
, NULL
);
612 /* if we got a directory name, then recurse down into it */
613 if (g_file_test (path
, G_FILE_TEST_IS_DIR
))
614 found
= find_element (path
, element
);
616 /* otherwise assume it is a file and see if it is the one we want */
619 printf ("\t : %s\t", name
);
620 if (!strcmp (name
, element
))
621 found
= g_strdup (path
);
624 tmps
= g_strconcat (element
, ".fp", NULL
);
625 if (!strcmp (name
, tmps
))
626 found
= g_strdup (path
);
630 printf ("%s\n", found
? "Yes" : "No");
641 search_element_directories (PcbElement
* el
)
644 gchar
*s
, *elname
= NULL
, *dir_path
, *path
= NULL
;
647 /* See comment before pkg_to_element() */
648 if (el
->pkg_name_fix
) {
649 if (strchr (el
->description
, '-')) {
650 n1
= strlen (el
->description
);
651 n2
= strlen (el
->pkg_name_fix
);
652 s
= el
->description
+ n1
- n2
- 1;
654 // printf("n1=%d n2=%d desc:%s fix:%s s:%s\n",
655 // n1, n2, el->description, el->pkg_name_fix, s);
657 if (n1
> 0 && n2
< n1
&& *s
== '-' && *(s
+ 1) == *el
->pkg_name_fix
) {
658 s
= g_strndup (el
->description
, n1
- n2
- 1);
659 elname
= g_strconcat (s
, " ", el
->pkg_name_fix
, NULL
);
664 printf ("Warning: argument passing may have been confused by\n");
665 printf (" a comma in a component value:\n");
666 printf (" Check %s %s %s\n",
667 el
->refdes
, el
->description
, el
->value
);
668 printf (" Maybe just use a space instead of a comma?\n");
672 elname
= g_strdup (el
->description
);
674 if (!strcmp (elname
, "unknown")) {
679 printf ("\tSearching directories looking for file element: %s\n", elname
);
680 for (list
= element_directory_list
; list
; list
= g_list_next (list
)) {
681 dir_path
= (gchar
*) list
->data
;
683 printf ("\tLooking in directory: \"%s\"\n", dir_path
);
684 path
= find_element (dir_path
, elname
);
687 printf ("\tFound: %s\n", path
);
695 /* The gnetlist backend gnet-gsch2pcb.scm generates PKG_ lines:
697 * PKG_footprint(footprint{-fp0-fp1},refdes,value{,fp0,fp1})
699 * where fp1 and fp2 (if they exist) are the extra footprint
700 * components when specifying footprints like "DIL 14 300". This is
701 * needed for m4 macros.
703 * A complication is if the footprint references a file element with
704 * spaces embedded in the name. The gnetlist backend will interpret
705 * these as fp0, fp1, ... args and the footprint will in this case
706 * incorrectly have '-' inserted where the spaces should be. So, if
707 * there are additional args, reconstruct the portion of the name
708 * given by the args with spaces for later use. Eg. if the footprint
709 * is "100 Pin jack", we will have
711 * PKG_100-Pin-jack(100-Pin-jack,refdes,value,Pin,jack)
713 * So put "Pin jack" into pkg_name_fix so if this element is searched
714 * as a file element we can munge the description to what it should
717 * 100-Pin-jack -> 100 Pin jack
720 pkg_to_element (FILE * f
, gchar
* pkg_line
)
724 gint n
, n_extra_args
, n_dashes
;
726 if (strncmp (pkg_line
, "PKG_", 4)
727 || (s
= strchr (pkg_line
, (gint
) '(')) == NULL
)
730 args
= g_strsplit (s
+ 1, ",", 12);
731 if (!args
[0] || !args
[1] || !args
[2]) {
732 fprintf (stderr
, "Bad package line: %s\n", pkg_line
);
735 fix_spaces (args
[0]);
736 fix_spaces (args
[1]);
737 fix_spaces (args
[2]);
739 el
= g_new0 (PcbElement
, 1);
740 el
->description
= g_strdup (args
[0]);
741 el
->refdes
= g_strdup (args
[1]);
742 el
->value
= g_strdup (args
[2]);
743 if ((s
= strchr (el
->value
, (gint
) ')')) != NULL
)
746 /* If the component value has a comma, eg "1k, 1%", the gnetlist generated
749 * PKG_XXX(`R0w8',`R100',`1k, 1%'),
751 * but after processed by m4, the input to gsch2pcb will be
753 * PKG_XXX(R0w8,R100,1k, 1%).
755 * So the quoting info has been lost when processing for file
756 * elements. So here try to detect and fix this. But I can't
757 * handle the situation where the description has a '-' and the
758 * value has a comma because gnet-gsch2pcb.scm munges the
759 * description with '-' when there are extra args.
761 for (n_extra_args
= 0; args
[3 + n_extra_args
] != NULL
; ++n_extra_args
);
763 for (n_dashes
= 0; (s
= strchr (s
+ 1, '-')) != NULL
; ++n_dashes
);
766 if (n_extra_args
== n_dashes
+ 1) { /* Assume there was a comma in the value, eg "1K, 1%" */
768 el
->value
= g_strconcat (s
, ",", fix_spaces (args
[n
]), NULL
);
770 if ((s
= strchr (el
->value
, (gint
) ')')) != NULL
)
775 el
->pkg_name_fix
= g_strdup (args
[n
]);
776 for (n
+= 1; args
[n
] != NULL
; ++n
) {
777 s
= el
->pkg_name_fix
;
778 el
->pkg_name_fix
= g_strconcat (s
, " ", args
[n
], NULL
);
781 if ((s
= strchr (el
->pkg_name_fix
, (gint
) ')')) != NULL
)
786 if (empty_footprint_name
&& !strcmp (el
->description
, empty_footprint_name
)) {
789 ("%s: has the empty footprint attribute \"%s\" so won't be in the layout.\n",
790 el
->refdes
, el
->description
);
793 } else if (!strcmp (el
->description
, "none")) {
795 "WARNING: %s has a footprint attribute \"%s\" so won't be in the layout.\n",
796 el
->refdes
, el
->description
);
799 } else if (!strcmp (el
->description
, "unknown")) {
801 "WARNING: %s has no footprint attribute so won't be in the layout.\n",
809 /* Process the newly created pcb file which is the output from
810 * gnetlist -g gsch2pcb ...
812 * It will have elements found via the m4 interface and PKG_ lines for
813 * elements not found. Insert pcb file elements for PKG_ lines if
814 * file elements can be found. If there was an existing pcb file,
815 * strip out any elements if they are already present so that the new
816 * pcb file will only have new elements.
819 add_elements (gchar
* pcb_file
)
822 PcbElement
*el
= NULL
;
823 gchar
*command
, *p
, *tmp_file
, *s
, buf
[1024];
824 gint total
, paren_level
= 0;
825 gboolean is_m4
, skipping
= FALSE
;
827 if ((f_in
= fopen (pcb_file
, "r")) == NULL
)
829 tmp_file
= g_strconcat (pcb_file
, ".tmp", NULL
);
830 if ((f_out
= fopen (tmp_file
, "wb")) == NULL
) {
835 while ((fgets (buf
, sizeof (buf
), f_in
)) != NULL
) {
836 for (s
= buf
; *s
== ' ' || *s
== '\t'; ++s
);
840 else if (*s
== ')' && --paren_level
<= 0)
845 if ((el
= pcb_element_line_parse (s
)) != NULL
)
848 el
= pkg_to_element (f_out
, s
);
849 if (el
&& pcb_element_exists (el
, TRUE
)) {
851 pcb_element_free (el
);
854 if (!el
|| el
->omit_PKG
) {
861 if (!is_m4
|| (is_m4
&& force_element_files
)) {
862 if (verbose
&& !is_m4
)
863 printf ("%s: need new file element for footprint %s (value=%s)\n",
864 el
->refdes
, el
->description
, el
->value
);
865 if (verbose
&& is_m4
&& force_element_files
)
867 ("%s: have m4 element %s, but trying to replace with a file element.\n",
868 el
->refdes
, el
->description
);
869 p
= search_element_directories (el
);
870 if (!p
&& verbose
&& is_m4
&& force_element_files
)
871 printf ("\tNo file element found.\n");
873 if (p
&& insert_element (f_out
, p
,
874 el
->description
, el
->refdes
, el
->value
)) {
879 printf ("%s: added new file element for footprint %s (value=%s)\n",
880 el
->refdes
, el
->description
, el
->value
);
883 "%s: can't find PCB element for footprint %s (value=%s)\n",
884 el
->refdes
, el
->description
, el
->value
);
885 if (remove_unfound_elements
&& !fix_elements
) {
887 "So device %s will not be in the layout.\n", el
->refdes
);
891 fputs (buf
, f_out
); /* Copy PKG_ line */
900 printf ("%s: added new m4 element for footprint %s (value=%s)\n",
901 el
->refdes
, el
->description
, el
->value
);
903 pcb_element_free (el
);
910 total
= n_added_ef
+ n_added_m4
+ n_not_found
;
912 build_and_run_command ("rm %s", tmp_file
);
914 build_and_run_command ("mv %s %s", tmp_file
, pcb_file
);
920 update_element_descriptions (gchar
* pcb_file
, gchar
* bak
)
924 PcbElement
*el
, *el_exists
;
925 gchar
*fmt
, *command
, *tmp
, *s
, buf
[1024];
927 for (list
= pcb_element_list
; list
; list
= g_list_next (list
)) {
928 el
= (PcbElement
*) list
->data
;
929 if (el
->changed_description
)
932 if (!pcb_element_list
|| n_fixed
== 0) {
933 fprintf (stderr
, "Could not find any elements to fix.\n");
936 if ((f_in
= fopen (pcb_file
, "r")) == NULL
)
938 tmp
= g_strconcat (pcb_file
, ".tmp", NULL
);
939 if ((f_out
= fopen (tmp
, "wb")) == NULL
) {
943 while ((fgets (buf
, sizeof (buf
), f_in
)) != NULL
) {
944 for (s
= buf
; *s
== ' ' || *s
== '\t'; ++s
);
945 if ((el
= pcb_element_line_parse (s
)) != NULL
946 && (el_exists
= pcb_element_exists (el
, FALSE
)) != NULL
947 && el_exists
->changed_description
) {
948 fmt
= el
->quoted_flags
?
949 "Element%c\"%s\" \"%s\" \"%s\" \"%s\" %s %s%s\n" :
950 "Element%c%s \"%s\" \"%s\" \"%s\" %s %s%s\n";
953 el
->flags
, el_exists
->changed_description
,
954 el
->refdes
, el
->value
, el
->x
, el
->y
, el
->tail
);
955 printf ("%s: updating element Description: %s -> %s\n",
956 el
->refdes
, el
->description
, el_exists
->changed_description
);
957 el_exists
->still_exists
= TRUE
;
960 pcb_element_free (el
);
966 build_and_run_command ("mv %s %s", pcb_file
, bak
);
970 build_and_run_command ("mv %s %s", tmp
, pcb_file
);
975 prune_elements (gchar
* pcb_file
, gchar
* bak
)
979 PcbElement
*el
, *el_exists
;
980 gchar
*fmt
, *command
, *tmp
, *s
, buf
[1024];
981 gint paren_level
= 0;
982 gboolean skipping
= FALSE
;
984 for (list
= pcb_element_list
; list
; list
= g_list_next (list
)) {
985 el
= (PcbElement
*) list
->data
;
986 if (!el
->still_exists
) {
990 "Preserving PCB element not in the schematic: %s (element %s)\n",
991 el
->refdes
, el
->description
);
994 } else if (el
->changed_value
)
997 if (!pcb_element_list
998 || (n_deleted
== 0 && !need_PKG_purge
&& n_changed_value
== 0)
1001 if ((f_in
= fopen (pcb_file
, "r")) == NULL
)
1003 tmp
= g_strconcat (pcb_file
, ".tmp", NULL
);
1004 if ((f_out
= fopen (tmp
, "wb")) == NULL
) {
1008 while ((fgets (buf
, sizeof (buf
), f_in
)) != NULL
) {
1009 for (s
= buf
; *s
== ' ' || *s
== '\t'; ++s
);
1013 else if (*s
== ')' && --paren_level
<= 0)
1018 if ((el
= pcb_element_line_parse (s
)) != NULL
1019 && (el_exists
= pcb_element_exists (el
, FALSE
)) != NULL
1020 && !el_exists
->still_exists
&& !preserve
) {
1023 printf ("%s: deleted element %s (value=%s)\n",
1024 el
->refdes
, el
->description
, el
->value
);
1025 pcb_element_free (el
);
1028 if (el_exists
&& el_exists
->changed_value
) {
1029 fmt
= el
->quoted_flags
?
1030 "Element%c\"%s\" \"%s\" \"%s\" \"%s\" %s %s%s\n" :
1031 "Element%c%s \"%s\" \"%s\" \"%s\" %s %s%s\n";
1032 fprintf (f_out
, fmt
,
1033 el
->res_char
, el
->flags
, el
->description
, el
->refdes
,
1034 el_exists
->changed_value
, el
->x
, el
->y
, el
->tail
);
1036 printf ("%s: changed element %s value: %s -> %s\n",
1037 el
->refdes
, el
->description
,
1038 el
->value
, el_exists
->changed_value
);
1039 } else if (!strncmp (s
, "PKG_", 4))
1040 ++n_PKG_removed_old
;
1043 pcb_element_free (el
);
1049 build_and_run_command ("mv %s %s", pcb_file
, bak
);
1053 build_and_run_command ("mv %s %s", tmp
, pcb_file
);
1058 add_m4_file (gchar
* arg
)
1063 m4_files
= g_strdup (arg
);
1066 m4_files
= g_strconcat (m4_files
, " ", arg
, NULL
);
1072 expand_dir (gchar
* dir
)
1078 s
= g_build_filename ((gchar
*) g_get_home_dir (), dir
+ 1, NULL
);
1085 add_default_m4_files (void)
1089 path
= g_build_filename ((gchar
*) g_get_home_dir (),
1090 ".pcb", DEFAULT_PCB_INC
, NULL
);
1091 if (g_file_test (path
, G_FILE_TEST_IS_REGULAR
))
1095 if (g_file_test (DEFAULT_PCB_INC
, G_FILE_TEST_IS_REGULAR
))
1096 add_m4_file (DEFAULT_PCB_INC
);
1101 add_schematic (gchar
* sch
)
1104 schematics
= g_list_append (schematics
, g_strdup (sch
));
1105 if (!sch_basename
&& (s
= g_strrstr (sch
, ".sch")) != NULL
&& strlen(s
) == 4)
1106 sch_basename
= g_strndup (sch
, s
- sch
);
1110 add_multiple_schematics (gchar
* sch
)
1112 /* parse the string using shell semantics */
1114 gchar
** args
= NULL
;
1115 GError
* error
= NULL
;
1117 if (g_shell_parse_argv (sch
, &count
, &args
, &error
)) {
1119 for (i
= 0; i
< count
; ++i
)
1121 add_schematic (args
[i
]);
1126 "invalid `schematics' option: %s\n",
1128 g_error_free (error
);
1133 parse_config (gchar
* config
, gchar
* arg
)
1137 /* remove trailing white space otherwise strange things can happen */
1138 if ((arg
!= NULL
) && (strlen (arg
) >= 1)) {
1139 s
= arg
+ strlen (arg
) - 1;
1140 while ((*s
== ' ' || *s
== '\t') && (s
!= arg
))
1146 printf (" %s \"%s\"\n", config
, arg
? arg
: "");
1148 if (!strcmp (config
, "remove-unfound") || !strcmp (config
, "r")) {
1149 /* This is default behavior set in header section */
1150 remove_unfound_elements
= TRUE
;
1153 if (!strcmp (config
, "keep-unfound") || !strcmp (config
, "k")) {
1154 remove_unfound_elements
= FALSE
;
1157 if (!strcmp (config
, "quiet") || !strcmp (config
, "q")) {
1161 if (!strcmp (config
, "preserve") || !strcmp (config
, "p")) {
1165 if (!strcmp (config
, "use-files") || !strcmp (config
, "f")) {
1166 force_element_files
= TRUE
;
1169 if (!strcmp (config
, "skip-m4") || !strcmp (config
, "s")) {
1173 if (!strcmp (config
, "elements-dir") || !strcmp (config
, "d")) {
1174 gchar
*elements_dir
= expand_dir (arg
);
1176 printf ("\tAdding directory to file element directory list: %s\n",
1178 element_directory_list
=
1179 g_list_prepend (element_directory_list
, elements_dir
);
1180 } else if (!strcmp (config
, "output-name") || !strcmp (config
, "o"))
1181 sch_basename
= g_strdup (arg
);
1182 else if (!strcmp (config
, "schematics"))
1183 add_multiple_schematics (arg
);
1184 else if (!strcmp (config
, "m4-pcbdir")) {
1186 m4_pcbdir
= g_strdup (arg
);
1187 } else if (!strcmp (config
, "m4-file"))
1189 else if (!strcmp (config
, "gnetlist"))
1190 extra_gnetlist_list
= g_list_append (extra_gnetlist_list
, g_strdup (arg
));
1191 else if (!strcmp (config
, "empty-footprint"))
1192 empty_footprint_name
= g_strdup (arg
);
1200 load_project (gchar
* path
)
1203 gchar
*s
, buf
[1024], config
[32], arg
[768];
1205 f
= fopen (path
, "r");
1209 printf ("Reading project file: %s\n", path
);
1210 while (fgets (buf
, sizeof (buf
), f
)) {
1211 for (s
= buf
; *s
== ' ' || *s
== '\t' || *s
== '\n'; ++s
);
1212 if (!*s
|| *s
== '#' || *s
== '/' || *s
== ';')
1215 sscanf (s
, "%31s %767[^\n]", config
, arg
);
1216 parse_config (config
, arg
);
1222 load_extra_project_files (void)
1225 static gboolean done
= FALSE
;
1230 load_project ("/etc/gsch2pcb");
1231 load_project ("/usr/local/etc/gsch2pcb");
1233 path
= g_build_filename ((gchar
*) g_get_home_dir (), ".gEDA",
1235 load_project (path
);
1241 static gchar
*usage_string0
=
1242 "usage: gsch2pcb [options] {project | foo.sch [foo1.sch ...]}\n"
1244 "Generate a PCB layout file from a set of gschem schematics.\n"
1245 " gnetlist -g PCB is run to generate foo.net from the schematics.\n"
1247 " gnetlist -g gsch2pcb is run to get PCB m4 derived elements which\n"
1248 " match schematic footprints. For schematic footprints which don't match\n"
1249 " any PCB m4 layout elements, search a set of file element directories in\n"
1250 " an attempt to find matching PCB file elements.\n"
1251 " Output to foo.pcb if it doesn't exist. If there is a current foo.pcb,\n"
1252 " output only new elements to foo.new.pcb.\n"
1253 " If any elements with a non-empty element name in the current foo.pcb\n"
1254 " have no matching schematic component, then remove those elements from\n"
1255 " foo.pcb and rename foo.pcb to a foo.pcb.bak sequence.\n"
1257 " gnetlist -g pcbpins is run to get a PCB actions file which will rename all\n"
1258 " of the pins in a .pcb file to match pin names from the schematic.\n"
1260 " \"project\" is a file (not ending in .sch) containing a list of\n"
1261 " schematics to process and some options. A schematics line is like:\n"
1262 " schematics foo1.sch foo2.sch ...\n"
1263 " Options in a project file are like command line args without the \"-\":\n"
1264 " output-name myproject\n"
1266 "options (may be included in a project file):\n"
1267 " -d, --elements-dir D Search D for PCB file elements. These defaults\n"
1268 " are searched if they exist: ./packages,\n"
1269 " /usr/local/share/pcb/newlib, /usr/share/pcb/newlib,\n"
1270 " (old pcb) /usr/local/lib/pcb_lib, /usr/lib/pcb_lib,\n"
1271 " (old pcb) /usr/local/pcb_lib\n"
1272 " -o, --output-name N Use output file names N.net, N.pcb, and N.new.pcb\n"
1273 " instead of foo.net, ... where foo is the basename\n"
1274 " of the first command line .sch file.\n"
1275 " -f, --use-files Force using file elements over m4 PCB elements\n"
1276 " for new footprints even though m4 elements are\n"
1277 " searched for first and may have been found.\n"
1278 " -r, --remove-unfound Don't include references to unfound elements in\n"
1279 " the generated .pcb files. Use if you want PCB to\n"
1280 " be able to load the (incomplete) .pcb file.\n"
1281 " This is the default behavior.\n"
1282 " -k, --keep-unfound Keep include references to unfound elements in\n"
1283 " the generated .pcb files. Use if you want to hand\n"
1284 " edit or otherwise preprocess the generated .pcb file\n"
1285 " before running pcb.\n"
1286 " -p, --preserve Preserve elements in PCB files which are not found\n"
1287 " in the schematics. Note that elements with an empty\n"
1288 " element name (schematic refdes) are never deleted,\n"
1289 " so you really shouldn't need this option.\n"
1290 " -q, --quiet Don't tell the user what to do next after running gsch2pcb.\n"
1292 " -s, --skip-m4 Skip m4 when looking for footprints. The default is to use\n"
1293 " m4 (which is what previous versions did).\n"
1294 " --m4-file F.inc Use m4 file F.inc in addition to the default m4\n"
1295 " files ./pcb.inc and ~/.pcb/pcb.inc.\n"
1296 " --m4-pcbdir D Use D as the PCB m4 files install directory\n"
1297 " instead of the default:\n";
1299 static gchar
*usage_string1
=
1300 " --gnetlist backend A convenience run of extra gnetlist -g commands.\n"
1301 " Example: gnetlist partslist3\n"
1302 " Creates: myproject.partslist3\n"
1303 " --empty-footprint name See the project.sample file.\n"
1305 "options (not recognized in a project file):\n"
1306 " --gnetlist-arg arg Allows additional arguments to be passed to gnetlist.\n"
1307 " --fix-elements If a schematic component footprint is not equal\n"
1308 " to its PCB element Description, update the\n"
1309 " Description instead of replacing the element.\n"
1310 " Do this the first time gsch2pcb is used with\n"
1311 " PCB files originally created with gschem2pcb.\n"
1312 " -v, --verbose Use -v -v for additional file element debugging.\n"
1313 " -V, --version\n\n"
1314 "environment variables:\n"
1315 " GNETLIST If set, this specifies the name of the gnetlist program\n"
1318 "Additional Resources:\n"
1320 " gnetlist user guide: http://wiki.geda-project.org/geda:gnetlist_ug\n"
1321 " gEDA homepage: http://www.geda-project.org\n"
1322 " PCB homepage: http://pcb.geda-project.org\n" "\n";
1327 puts (usage_string0
);
1328 printf (" %s\n\n", default_m4_pcbdir
);
1329 puts (usage_string1
);
1334 get_args (gint argc
, gchar
** argv
)
1336 gchar
*opt
, *arg
, *s
;
1339 for (i
= 1; i
< argc
; ++i
) {
1346 if (!strcmp (opt
, "version") || !strcmp (opt
, "V")) {
1347 printf ("gsch2pcb %s\n", GSC2PCB_VERSION
);
1349 } else if (!strcmp (opt
, "verbose") || !strcmp (opt
, "v")) {
1352 } else if (!strcmp (opt
, "fix-elements")) {
1353 fix_elements
= TRUE
;
1355 } else if (!strcmp (opt
, "gnetlist-arg")) {
1356 extra_gnetlist_arg_list
=
1357 g_list_append (extra_gnetlist_arg_list
, g_strdup (arg
));
1360 } else if (!strcmp (opt
, "help") || !strcmp (opt
, "h"))
1363 && ((r
= parse_config (opt
, (i
< argc
- 1) ? arg
: NULL
))
1369 printf ("gsch2pcb: bad or incomplete arg: %s\n", argv
[i
]);
1372 if (!g_str_has_suffix (argv
[i
], ".sch")) {
1373 load_extra_project_files ();
1374 load_project (argv
[i
]);
1376 add_schematic (argv
[i
]);
1382 main (gint argc
, gchar
** argv
)
1384 gchar
*pcb_file_name
,
1385 *pcb_new_file_name
, *bak_file_name
, *pins_file_name
, *net_file_name
, *tmp
;
1387 gboolean initial_pcb
= TRUE
;
1388 gboolean created_pcb_file
= TRUE
;
1390 const char *pcbdata_path
;
1391 const char *configure_m4_pcbdir
= PCBM4DIR
; /* do not free it */
1393 pcbdata_path
= g_getenv ("PCBDATA"); /* do not free return value */
1394 if (pcbdata_path
!= NULL
) {
1395 /* If PCBDATA is set, use the value */
1396 m4_pcbdir
= g_strconcat (pcbdata_path
, "/m4", NULL
);
1397 } else if (configure_m4_pcbdir
!= NULL
) {
1398 /* Use the default value passed in from the configure script
1399 * instead of trying to hard code a value which is very
1402 m4_pcbdir
= g_strdup (configure_m4_pcbdir
);
1404 /* Neither PCBDATA was set nor PCBM4DIR has been configured */
1405 /* Fall back to using the "m4" subdirectory in the current directory */
1406 m4_pcbdir
= g_strdup ("./m4");
1409 default_m4_pcbdir
= g_strdup (m4_pcbdir
);
1414 get_args (argc
, argv
);
1416 load_extra_project_files ();
1417 add_default_m4_files ();
1423 /* Defaults for the search path if not configured in the project file */
1424 if (g_file_test ("packages", G_FILE_TEST_IS_DIR
))
1425 element_directory_list
= g_list_append (element_directory_list
, "packages");
1427 #define PCB_PATH_DELIMETER ":"
1429 printf ("Processing PCBLIBPATH=\"%s\"\n", PCBLIBPATH
);
1431 path
= g_strdup (PCBLIBPATH
);
1432 for (p
= strtok (path
, PCB_PATH_DELIMETER
); p
&& *p
;
1433 p
= strtok (NULL
, PCB_PATH_DELIMETER
)) {
1434 if (g_file_test (p
, G_FILE_TEST_IS_DIR
)) {
1436 printf ("Adding %s to the newlib search path\n", p
);
1437 element_directory_list
= g_list_append (element_directory_list
,
1443 pins_file_name
= g_strconcat (sch_basename
, ".cmd", NULL
);
1444 net_file_name
= g_strconcat (sch_basename
, ".net", NULL
);
1445 pcb_file_name
= g_strconcat (sch_basename
, ".pcb", NULL
);
1446 bak_file_name
= g_strconcat (sch_basename
, ".pcb.bak", NULL
);
1447 tmp
= g_strdup (bak_file_name
);
1449 for (i
= 0; g_file_test (bak_file_name
, G_FILE_TEST_EXISTS
); ++i
) {
1450 g_free (bak_file_name
);
1451 bak_file_name
= g_strdup_printf ("%s%d", tmp
, i
);
1455 if (g_file_test (pcb_file_name
, G_FILE_TEST_EXISTS
)) {
1456 initial_pcb
= FALSE
;
1457 pcb_new_file_name
= g_strconcat (sch_basename
, ".new.pcb", NULL
);
1458 get_pcb_element_list (pcb_file_name
);
1460 pcb_new_file_name
= g_strdup (pcb_file_name
);
1462 if (!run_gnetlist (pins_file_name
, net_file_name
, pcb_new_file_name
,
1463 sch_basename
, schematics
)) {
1464 fprintf(stderr
, "Failed to run gnetlist\n");
1468 if (add_elements (pcb_new_file_name
) == 0) {
1469 build_and_run_command ("rm %s", pcb_new_file_name
);
1471 printf ("No elements found, so nothing to do.\n");
1477 update_element_descriptions (pcb_file_name
, bak_file_name
);
1478 prune_elements (pcb_file_name
, bak_file_name
);
1480 /* Report work done during processing */
1483 printf ("\n----------------------------------\n");
1484 printf ("Done processing. Work performed:\n");
1485 if (n_deleted
> 0 || n_fixed
> 0 || need_PKG_purge
|| n_changed_value
> 0)
1486 printf ("%s is backed up as %s.\n", pcb_file_name
, bak_file_name
);
1487 if (pcb_element_list
&& n_deleted
> 0)
1488 printf ("%d elements deleted from %s.\n", n_deleted
, pcb_file_name
);
1490 if (n_added_ef
+ n_added_m4
> 0)
1491 printf ("%d file elements and %d m4 elements added to %s.\n",
1492 n_added_ef
, n_added_m4
, pcb_new_file_name
);
1493 else if (n_not_found
== 0) {
1494 printf ("No elements to add so not creating %s\n", pcb_new_file_name
);
1495 created_pcb_file
= FALSE
;
1498 if (n_not_found
> 0) {
1499 printf ("%d not found elements added to %s.\n",
1500 n_not_found
, pcb_new_file_name
);
1503 printf ("%d components had no footprint attribute and are omitted.\n",
1506 printf ("%d components with footprint \"none\" omitted from %s.\n",
1507 n_none
, pcb_new_file_name
);
1509 printf ("%d components with empty footprint \"%s\" omitted from %s.\n",
1510 n_empty
, empty_footprint_name
, pcb_new_file_name
);
1511 if (n_changed_value
> 0)
1512 printf ("%d elements had a value change in %s.\n",
1513 n_changed_value
, pcb_file_name
);
1515 printf ("%d elements fixed in %s.\n", n_fixed
, pcb_file_name
);
1516 if (n_PKG_removed_old
> 0) {
1517 printf ("%d elements could not be found.", n_PKG_removed_old
);
1518 if (created_pcb_file
)
1519 printf (" So %s is incomplete.\n", pcb_file_name
);
1523 if (n_PKG_removed_new
> 0) {
1524 printf ("%d elements could not be found.", n_PKG_removed_new
);
1525 if (created_pcb_file
)
1526 printf (" So %s is incomplete.\n", pcb_new_file_name
);
1530 if (n_preserved
> 0)
1531 printf ("%d elements not in the schematic preserved in %s.\n",
1532 n_preserved
, pcb_file_name
);
1534 /* Tell user what to do next */
1538 if (n_added_ef
+ n_added_m4
> 0) {
1540 printf ("\nNext step:\n");
1541 printf ("1. Run pcb on your file %s.\n", pcb_file_name
);
1543 (" You will find all your footprints in a bundle ready for you to place\n");
1545 (" or disperse with \"Select -> Disperse all elements\" in PCB.\n\n");
1547 ("2. From within PCB, select \"File -> Load netlist file\" and select \n");
1548 printf (" %s to load the netlist.\n\n", net_file_name
);
1549 printf ("3. From within PCB, enter\n\n");
1550 printf (" :ExecuteFile(%s)\n\n", pins_file_name
);
1552 (" to propagate the pin names of all footprints to the layout.\n\n");
1553 } else if (quiet_mode
== FALSE
) {
1554 printf ("\nNext steps:\n");
1555 printf ("1. Run pcb on your file %s.\n", pcb_file_name
);
1557 ("2. From within PCB, select \"File -> Load layout data to paste buffer\"\n");
1559 (" and select %s to load the new footprints into your existing layout.\n",
1562 ("3. From within PCB, select \"File -> Load netlist file\" and select \n");
1563 printf (" %s to load the updated netlist.\n\n", net_file_name
);
1564 printf ("4. From within PCB, enter\n\n");
1565 printf (" :ExecuteFile(%s)\n\n", pins_file_name
);
1566 printf (" to update the pin names of all footprints.\n\n");
1570 g_free (net_file_name
);
1571 g_free (pins_file_name
);
1572 g_free (pcb_file_name
);
1573 g_free (bak_file_name
);