#usage "en: Output of a Schematic (or Board) as " "Windows Meta File " "for convenient embedding into office documents and searchable strings in resulting PDFs

\n" "Only visible layers will be processed.

" "Command-line arguments are: [arguments] [filename]
\n" "Possible arguments see SCH2WMF.TXT.\n" "


" "Author: Henrik Haftmann,
" "latest version: TU Chemnitz
", "de: Ausgabe eines Schaltplanes (oder Boards) als " "Windows-Meta-Datei (File) " "für bequemen Dokument-Import sowie String-Suchmöglichkeit in PDFs

\n" "Es werden nur die gerade sichtbaren Layer ausgegeben.
" "Kommandozeilenargumente: [Argumente] [Dateiname]
\n" "Argumente siehe SCH2WMF.TXT.\n" "


" "Autor: " "henrik.haftmann@e-technik.tu-chemnitz.de,
" "URL: " "http://www.tu-chemnitz.de/~heha/hs_freeware/sch2wmf/
" #require 6.00 string I18N[] = { "en de", "+Ok +Ok", "-Cancel -Abbrechen", /*3*/ "Using vector font results in a perfectly scaleable output file.

" "But the resulting file is much larger in size " "and texts are not searchable because vectorized.

" "The settings Bold and Italic are not applicable.\t" "Die Vektor-Schriftart ergibt layout-treue Ausgabedateien.

" "Allerdings ist die WMF-Datei wesentlich größer " "und nicht durchsuchbar nach Text.

" "Die Einstellungen Fett und Kursiv sind wirkungslos.", "Vector Vektor", "Bold Fett", "Italic Kursiv", /*7*/ "(transparent) (transparent)", /*8*/ "(opaque) (deckend)", /*9*/ "EMF export options EMF-Export-Optionen", /*10*/ "file name: %s Dateiname: %s", /*11*/ "Output colors (see Eagle palette) Ausgabe-&Farben (siehe Eagle-Palette)", "Black on white (for monochrome printers) Schwarz auf Weiß (Schwarzweiß)", "Colors on black Bunt auf Schwarz", "Colors on white Bunt auf Weiß", "Colors on colored background Bunt auf farbigem Hintergrund", /*16*/ "Font &replacement (used fonts are listed here) " "Schrift-&Ersetzung (verwendete Schriften gelistet)", /*17*/ "Note: Vector font on copper will be never replaced " "Hinweis: Vektorschrift auf Kupfer wird niemals ersetzt.", /*18*/ "&Hatch style (%d hatched layer(s) present) " "Füll&muster (%d schraffierte(r) Layer vorhanden)", /*19*/ "Filled Ausgefüllt", /*20*/ "Windows hatch style (6 predefined: = || // \\\\\\\\ ++ xx) " "Windows-Füllmuster (hat nur 6 Muster: = || // \\\\\\\\ ++ xx)", /*21*/ "Exact hatch like in Eagle " "Exakte Füllmuster wie in Eagle", /*22*/ "Page %d Layer %d (%s) " "Seite %d Layer %d (%s)", /*23*/ "Layer %d (%s) " "Layer %d (%s)" }; int Language = strstr(I18N[0], language()) / 3; string tr(string s) { string t = lookup(I18N, s, Language); return t ? t : s; } string LoadString(int n) { string a[]; n=strsplit(a,I18N[n],'\t'); return a[Language255) break; // Fehler while ('0'<=LastLayers[i] && LastLayers[i]<='9') i++; // ggf. Endwert einlesen if (LastLayers[i]=='-') { i++; layere=strtol(strsub(LastLayers,i)); if (layere<=0 || layere>255) break; // Fehler while ('0'<=LastLayers[i] && LastLayers[i]<='9') i++; }else layere=layer; // auf- oder absteigend LastLayers füllen if (layer<=layere) for (; layer<=layere; layer++) A[j++]=layer; else for (; layer>=layere; layer--) A[j++]=layer; // Folgezeichen auswerten if (!LastLayers[i]) break; // Ende if (LastLayers[i]!=',' || !LastLayers[i+1]) break; // Fehler, wenn kein Komma oder ",\0" } A[j]=0; // terminieren if (LastLayers[i]) { // Nicht am Ende angekommen? string s; sprintf(s,"!Falsche Syntax für Layer-Angabe!

%s\n%*s^---hier!",
    LastLayers,i,"");
  dlgMessageBox(s);
  return 0;
 }
 LastLayers=A;
 return 1;
}

/***********************
 *** Dialog-Routinen ***
 ***********************/
// globale Variablen: Farben und Füllstile sowie Name per Layer
int col[],fil[];
string nam[];

// liefert TRUE, wenn Layers aktiv sind, die von unten zu sehen sind
// (d.h. solche, auf denen platzierter Text tatsächlich gespiegelt erscheint)
int AnyBottomLayers() {
 return col[LAYER_BOTTOM]
      + col[LAYER_BPLACE]
      + col[LAYER_BORIGINS]
      + col[LAYER_BNAMES]
      + col[LAYER_BVALUES]
      + col[LAYER_BSTOP]
      + col[LAYER_BCREAM]
      + col[LAYER_BFINISH]
      + col[LAYER_BGLUE]
      + col[LAYER_BTEST]
      + col[LAYER_BKEEPOUT]
      + col[LAYER_BRESTRICT]
      + col[LAYER_BDOCU];
}

// Font-Namen nur für SetFontLabel (Leerzeichen führen zu Fehlern)
string FontSty[]={"Verdana","Arial","Courier","Times"};
void SetFontLabel(void) {
 for (int idx=0; idx<3; idx++) sprintf(FontLabel[idx],
   "%s:",
   FontSty[FontSel[idx]],
   FontBold[idx]?700:400,
   FontItalic[idx]?"italic":"normal",
   FontSrc[idx]);
}

void FontSubstLine(int idx) {
 dlgCell(idx,0) {dlgLabel(FontLabel[idx],1); dlgSpacing(10);}
 dlgCell(idx,1) {
  dlgComboBox(FontName,FontSel[idx]) {
   if (!FontSel[0]) dlgMessageBox(
     "Die Vektor-Schriftart ergibt layout-treue Ausgabedateien.

"+ "Allerdings ist die WMF-Datei wesentlich größer "+ "und nicht durchsuchbar nach Text.

"+ "Die Einstellungen Fett und Kursiv sind wirkungslos."); SetFontLabel(); } dlgSpacing(10); } dlgCell(idx,2) dlgCheckBox("Fett",FontBold[idx]) SetFontLabel(); dlgCell(idx,3) dlgCheckBox("Kursiv",FontItalic[idx]) SetFontLabel(); dlgCell(idx,4) dlgStretch(1); } void OnSetPaletteType(void) { switch (PaletteType) { case 1: { OutBkgnd=1; if (board) Trans=1; // voreinstellen wie Eagle if (NumExact) HatchType=2; }break; case 3: { OutBkgnd=!Trans; // voreinstellen wie Eagle (wenn Trans=0) }break; } } // Hilfsvariablen zum Zusammensetzen von Flipped int TopDown; int Rotated; int DialogBox(void) { string DeckLabel=Trans?"(transparent)":"(deckend)"; status("Dialog"); return dlgDialog("WMF-Export-Optionen") { dlgLabel("Dateiname: "+SName+""); if (FName) dlgLabel("Ausgabe-Datei: "+filename(FName)+""); if (Dialog>1) dlgGridLayout{ dlgCell(0,0) dlgLabel("WMF-Einheit (alles 16-Bit-Koordinaten): "); dlgCell(0,1) dlgRealEdit(Teiler,1,300); dlgCell(0,2) dlgLabel(" µm"); dlgCell(1,0) dlgLabel("Minimale Linienbreite für Ersetzungen: "); dlgCell(1,1) dlgRealEdit(MinBreite,0.1,3000); dlgCell(1,2) dlgLabel(" µm"); dlgCell(2,0) dlgLabel("Ausgabe-Skalierung (»falsche« DPI-Angabe): "); dlgCell(2,1) dlgRealEdit(Scale,0.1,100); } dlgGroup("Ausgabe-&Farben (siehe Eagle-Palette)") { dlgRadioButton("Schwarz auf Weiß (Schwarzweiß)",PaletteType) OnSetPaletteType(); dlgRadioButton("Bunt auf Schwarz",PaletteType) OnSetPaletteType(); dlgRadioButton("Bunt auf Weiß",PaletteType) OnSetPaletteType(); dlgRadioButton("Bunt auf farbigem Hintergrund",PaletteType) OnSetPaletteType(); } dlgGroup("Schrift-&Ersetzung (verwendete Schriften gelistet)") { dlgGridLayout{ SetFontLabel(); for (int i=0; i<3; i++) if (NumFont[i]) FontSubstLine(i); } if (NumCopperVector) { dlgSpacing(4); dlgLabel("Hinweis: Vektorschrift auf Kupfer wird niemals ersetzt."); } } if (Dialog>1) { if (NumFlatArc) { string s; sprintf(s,"Behandlung von %d B%sgen mit flachen Enden", NumFlatArc, NumFlatArc==1?"o":"ö"); dlgGroup(s) { dlgHBoxLayout{ dlgLabel("als Polygon interpolieren ab Breite:Radius: "); dlgRealEdit(FlatArcReplace,0,2); } dlgLabel("0 = immer (größere Datei), 2 = nie (sichtbare »Wurst-Enden«)"); } } if (NumPieced) { string s; sprintf(s,"Behandlung von %d Strichellinie%s", NumPieced,NumPieced==1?"":"n"); dlgGroup(s) { dlgRadioButton("Linien mit Windows-Strichelung ausgeben",PiecedReplace); dlgRadioButton("Windows für Linien und Bögen, Eagle für Polygone",PiecedReplace); dlgRadioButton("Eagle-Strichelung verwenden (Einzelsegmente)",PiecedReplace); } } } if (NumHatched) { string s; sprintf(s,"Füll&muster (%d schraffierte%s Layer vorhanden)", NumHatched,NumHatched==1?"r":""); dlgGroup(s) { dlgRadioButton("Ausgefüllt",HatchType) if (Dialog>1) LineReplace=0; dlgRadioButton("Windows-Füllmuster (hat nur 6 Muster: = || // \\\\\\\\ ++ xx)",HatchType); dlgHBoxLayout{ dlgRadioButton("Exakte Füllmuster wie in Eagle",HatchType); dlgLabel(DeckLabel,1); // "transparent" oder "deckend" dlgStretch(1); } // Vorher ermitteln, ob überhaupt problematische Linien oder Polygone // mit Strichbreite>0 vorhanden sind!? if (Dialog>1) { dlgSpacing(4); dlgGroup("Ersetzung von schraffierten &Linien und Bögen") { dlgRadioButton("Keine Ersetzung (dann ausgefüllt, nicht schraffiert)", LineReplace); dlgRadioButton("Linien und Bögen durch Polygone ersetzen", LineReplace); dlgRadioButton("Auch Polygonränder ersetzen", LineReplace); } } } } if (board) { if (AnyBottomLayers() || Dialog>1) { dlgCheckBox("&Gespiegelt (an Y-Achse, zur Seite)",Flipped); } if (Dialog>1) { dlgCheckBox("Na&ch unten gespiegelt (an X-Achse)",TopDown); dlgCheckBox("&90° nach links rotiert (nach den Spiegelungen)",Rotated); } dlgCheckBox("Simulierte Tr&ansparenz (mit Rasteroperationskodes)",Trans) { DeckLabel=Trans?"(transparent)":"(deckend)"; if (Trans) switch (PaletteType) { case 3: dlgMessageBox( "Bei »Bunt auf farbigem Hintergrund« gibt es in Eagle < 5 keine Transparenz.

" "Hier werden Komplementärfarben ausgegeben.

"+ "Hässliches Aussehen bei PostScript- und PDF-Weiterverarbeitung (Windows-Bug)"); break; } } if (Dialog>1 || LastLayers) { // Nur bei "Dialog=2" oder LastLayer-Vorgabe dlgGroup("&Umordnung der Layer-Ausgabe") { dlgHBoxLayout{ dlgLabel("Zuletzt ausgeben (stets deckend):"); dlgSpacing(4); dlgStringEdit(LastLayers); } } } } if (DoNegate) dlgCheckBox("&Negationsstriche (sonst »!«)",DoNegate); if (GroupOnly==1) dlgCheckBox("Selektierte G&ruppe (sonst ganze Seite)",GroupOnly); else GroupOnly=0; dlgCheckBox("&Hintergrund ausmalen (Vektorgrafik nicht transparent)",OutBkgnd); if (NumSheet>1) { dlgSpacing(4); dlgCheckBox("&Alle Seiten (mehrere Dateien)",AllPages) { if (AllPages) GroupOnly=0; } } dlgSpacing(4); dlgHBoxLayout { dlgPushButton(FName?"+O&K":"+&Weiter, zum Speichern ...") { if (Dialog<2 || CalcLastLayers()) dlgAccept(1); } dlgPushButton("Abbrechen") dlgReject(); } }; } /* Die Vektor-Schriftart und die (später) möglichen Zeichen-Ersetzungen Kode Zeichen (CP1252) CP437 Symbol Unicode 80 128 ? 81 129 Raster B1 177 82 130 ? 83 131 ? 84 132 ? 85 133 ? 86 134 ? 87 135 ? 88 136 ? 89 137 ? 8A 138 ? 8B 139 ? 8C 140 ? 8D 141 Rahmen:0-0-1-1 DA 218 U+250C 8E 142 ? 8F 143 größer-gleich F2 242 B3 179 90 144 Block oben DF 223 91 145 ? 92 146 ? 93 147 ? 94 148 ? 95 149 ? 96 150 ? 97 151 ? 98 152 ? 99 153 ? 9A 154 ? 9B 155 ? 9C 156 ? 9D 157 Rahmen:0-0-2-2 C9 201 U+2554 9E 158 ? 9F 159 ? A0 160 _ (anderer Kode) A1 161 ¡ A2 162 Rahmen:1-2-0-0 BD 189 U+255C A3 163 £ A4 164 Rahmen:2-1-2-0 D0 208 U+2567 A5 165 Rahmen:2-1-0-0 BE 190 U+255B A6 166 Block rechts? DE 222 A7 167 Integral untere Hälfte F5 245 A8 168 Block? unten DC 220 A9 169 Rahmen:2-0-0-1 B8 184 U+2555 AA 170 ª AB 171 « AC 172 ¬ AD 173 identisch F0 240 BA 186 AE 174 umgekehrte Negation A9 169 AF 175 Element-von EE 238 CE 206 B0 176 ° B1 177 ± B2 178 ² B3 179 hochgestelltes n FC 252 B4 180 Schnittmenge EF 239 C7 199 B5 181 µ B6 182 Integral obere Hälfte F4 244 B7 183 · (aber tiefer) B8 184 Rund (zwei Wellen) F7 247 BB 187 B9 185 Wurzel FB 251 D6 214 BA 186 º BB 187 » BC 188 ¼ BD 189 ½ BE 190 kleiner-gleich F3 243 A3 163 BF 191 ¿ C0 192 Rahmen:1-0-0-2 B7 183 U+2556 C1 193 Rahmen:2-1-0-1 B5 181 U+2561 C2 194 Rahmen:1-2-0-2 B6 182 U+2562 C3 195 Rahmen:0-2-1-2 C7 199 U+255F C4 196 Ä C5 197 Å C6 198 Æ C7 199 Ç C8 200 Rahmen:0-1-2-0 D4 212 U+2558 C9 201 É CA 202 Rahmen:1-0-1-2 D2 210 U+2565 CB 203 Rahmen:0-2-1-0 D3 211 U+2559 CC 204 Block rechts? DE 222 CD 205 Rahmen:0-0-1-2 D6 214 U+2553 CE 206 Rahmen:1-2-1-2 D7 215 U+256B CF 207 Rahmen:2-1-2-1 D8 216 U+256A D0 208 Rahmen:2-0-2-1 D1 209 U+2564 D1 209 Ñ D2 210 pi E3 227 70 112 D3 211 alpha E0 224 61 97 D4 212 Gamma E2 226 47 71 D5 213 rho E5 229 73 115 D6 214 Ö D7 215 Peseta 9D? D8 216 Ø D9 217 delta EB 235 64 100 DA 218 Theta E9 233 51 81 DB 219 Ohm EA 234 57 87 DC 220 Ü DD 221 ø (anderer Kode!) ED? 198 DE 222 Phi E8 232 46 70 DF 223 ß E0 224 à E1 225 á E2 226 â E3 227 Rahmen:0-1-2-1 C6 198 U+255E E4 228 ä E5 229 å E6 230 æ E7 231 ç E8 232 è E9 233 é EA 234 ê EB 235 Leerzeichen "nbsp" EC 236 ì ED 237 í EE 238 î EF 239 ï F0 240 Rahmen:1-2-1-0 D0 208 U+2568 F1 241 ñ F2 242 ò F3 243 ó F4 244 ô F5 245 Sigma E4 228 53 83 F6 246 ö F7 247 ÷ F8 248 ø F9 249 ù FA 250 ú FB 251 û FC 252 ü FD 253 unendlich EC 236 A5 165 FE 254 tau E7 231 74 116 FF 255 ÿ */ string Problem160= // Ausrufezeichen bei Problemstellen, "!¡!£!!¦!!!ª«¬º!ΰ±²!ǵ!·»Öº»¼½£¿" // ungleiche Zeichen bei Einsatz +"!!!!ÄÅÆÇ!É!!!!!!!ÑpaGsÖ!ØdQWÜÆFß" // der Schriftart „Symbol“ +"àáâ!äåæçèéê ìíîï!ñòóôSö÷øùúûü¥tÿ"; int ProblemChars(string s) { int len=strlen(s); for (int i=0; i=128 && c<160 && c!=143) return 1; //immer Problem if (c>=160 && Problem160[c-160]=='!') return 1; // bspw. Rahmenzeichen } return 0; } /* liefert TRUE bei problematischem Text: * Schriftart == Vector UND Ersatzschrift != Vector UND enthält Zeichen im Nicht-Latin1-Font * Ersatzschrift != Vector UND (Schrift gespiegelt XOR Ausgabe nicht gespiegelt) UND im Board --- WMF erlaubt leider keine Ausgabe von gespiegeltem Text --- */ int ProblemText(UL_TEXT O) { if (O.font==FONT_VECTOR/*0*/ && FontSel[0] && ProblemChars(O.value)) return 1; // Zweiter Fall nicht implementiert return 0; } // Kode für „Symbol“-Zeichensatz ermitteln, 0 wenn keine Symbol-Ersetzung int GetSymbolChar(char c) { if (c==143) return 179; if (c>=160) { char r=Problem160[c-160]; if (r!='!' && r!=c) return r; } return 0; } // Test des Strings auf Symbol-Zeichenersetzung int HasSymbolChars(string s) { int len=strlen(s); for (int i=0; i ab . // Ist !=0, dann SYMBOL-Zeichensatz, sonst ANSI-Zeichensatz // Bei Symbol-Zeichensatz liefert die Funktion die Kodes // des Symbol-Zeichensatzes. string GetCharRun(string s, int pos, int symb) { string ret=""; if (symb) for (;;) { char c=GetSymbolChar(s[pos++]); if (!c) break; ret+=c; }else for (;;) { char c=s[pos++]; if (!c) break; if (GetSymbolChar(c)) break; ret+=c; } return ret; } void CountFont(UL_TEXT O) { if (!col[O.layer]) return; // unsichtbare Schrift nicht mitzählen if (!strlen(O.value)) return; // Leere Strings nicht mitzählen NumFont[O.font]++; if (ProblemText(O)) FontSel[0]=0; // Vektor-Schrift voreinstellen (Default: Arial) if (O.font==FONT_VECTOR && FontSel[0] && FontSel[0]==FontSel[1] && HasSymbolChars(O.value)) { FontBold[0]=FontBold[1]; // Vorgabe: Soll gleich aussehen (typisch für Ohm-Zeichen) FontItalic[0]=FontItalic[1]; } if (!DoNegate) { int i=strchr(O.value,'!'); // Schnellsuche (unexakt) if (i!=-1 && i!=strlen(O.value)-1) DoNegate++; // Wenigstens Ausrufezeichen am Ende nicht beachten } } int IsPie(UL_ARC O) { return O.cap==CAP_FLAT && O.width>O.radius*1.9; } void CountFlatArc(UL_WIRE O) { if (O.cap==CAP_FLAT && O.arc && !IsPie(O.arc)) NumFlatArc++; if (O.style!=WIRE_STYLE_CONTINUOUS) NumPieced++; } /* Suche nach verwendeten Text-Fonts, zur Vorbereitung des Dialogs */ // Weiterhin wird (beim Vektor-Font) nach problematischen Zeichen gesucht. // Bei Problemen wird die Voreinstellung für Vektor-Font // von "Arial" auf "Vektor" zurückgesetzt. // (Für alle, die nicht wissen, worum es geht: // Eagle unterstützt bspw. das Omega-Zeichen, aber nur in der Vector-Schrift.) // In nicht-westeuropäischen Windows-Versionen sind größere Teile der // Vector-Schriftart problematisch ... // Außerdem wird nach Bögen mit CAP_FLAT gesucht void SchCountFonts(UL_SCHEMATIC O) { O.sheets(O){ string e; sprintf(e,"Seite %d",O.number); status(e); NumSheet++; O.busses(O) { O.segments(O) { O.texts(O) CountFont(O); O.wires(O) CountFlatArc(O); } } O.nets(O) { O.segments(O) { O.texts(O) CountFont(O); O.wires(O) CountFlatArc(O); } } O.parts(O) { O.instances(O) { O.gate.symbol.pins(O) { O.texts(O) CountFont(O); O.wires(O) CountFlatArc(O); } O.gate.symbol.frames(O) O.texts(O) CountFont(O); O.gate.symbol.dimensions(O) O.texts(O) CountFont(O); O.gate.symbol.texts(O) CountFont(O); O.gate.symbol.wires(O) CountFlatArc(O); O.texts(O) CountFont(O); } } O.texts(O) CountFont(O); O.wires(O) CountFlatArc(O); } } // das gleiche für's Board void BrdCountFonts(UL_BOARD O) { O.elements(O) { O.texts(O) CountFont(O); O.package.texts(O) CountFont(O); O.package.wires(O) CountFlatArc(O); } O.signals(O) O.wires(O) CountFlatArc(O); O.texts(O) { // Vektor-Schrift im Kupfer wird NICHT gezählt (bleibt stets Vektor) if (O.layer>LAYER_BOTTOM || O.font!=FONT_VECTOR) CountFont(O); else if (col[O.layer]) NumCopperVector++; } O.wires(O) CountFlatArc(O); } int IsRatsnestPoly(UL_POLYGON O) { O.fillings(O) return 1; return 0; } // Prüft auf Vorhandensein sichtbarer, nicht geRATSNESTer Polygone (nur Kupfer) int BrdAreRipupPolys(UL_BOARD O) { O.signals(O) O.polygons(O) if (O.layer<=LAYER_BOTTOM && col[O.layer] && !IsRatsnestPoly(O)) return 1; return 0; } // **** globale Variablen **** /* für den WMF-Kopf */ int nWords; // Länge WMF-Datei int nMaxRecSize;// Länge längster WMF-Record int nHandles; /* WMF-Daten (Ansammlung im RAM, WORD-Array) */ int FileData[]; int CurLayer; int LayerColor; // Windows-RGB int LayerFill; // Windows-Füllstil (Low-Nibble, High-Nibble) int LayerRop; // aktueller Windows-Rasteroperationskode int LastX,LastY; // (kleinere Datei, schnelleres Rendern) int TextColor; int TextAlign; int BackgroundColor; int DrawHoles=0; // Löcher statt Pads/Vias zeichnen /* Ausgabe 2 Byte in Intel-Notation (Little Endian) */ void PrintWord(int w) { printf("%c%c",w&0xFF,(w>>8)&0xFF); } /* Ausgabe 2 oder 4 Byte in Intel-Notation in Datenpuffer */ void OutWord(int w) { FileData[nWords++]=w; } void OutDword(int d) { FileData[nWords++]=d; FileData[nWords++]=d>>16; } // =1: String garantiert nullterminieren, sonst nicht zwangsweise nullterminiert void OutString(string s, int term) { int i, len=strlen(s)+term; for (i=0; i>16; } int Mass(int eaglepixel) { // 1 Eagle-Pixel sind 1/320 µm (8128000 dpi), damit gibts Probleme mit 16 bit. // Division durch 8128 ergibt 25,4 µm als Ersatzraster, 1/1000 Zoll = 1 mil, ergibt 1000 dpi, // das scheint für alle gängigen Schaltpläne (und auch Boards) ausreichend zu sein. // Die Maximalausdehnung wäre dann 1 mil * 32767 ~ 83 cm // Für 1200 dpi (Laserdrucker) müsste der Teiler 6773,3 sein. return round(u2mic(eaglepixel)/Teiler); } void OutMassX(int eaglepixel) { OutWord(Mass(Flipped&1?-eaglepixel:eaglepixel)); } void OutMassY(int eaglepixel) { OutWord(Mass(Flipped&2?eaglepixel:-eaglepixel)); } void OutXY(int x, int y) { if (Flipped&4) {int t=x; x=y; y=t;} OutMassY(y); // im WMF verkehrherum weil's auf Pascal-Aufrufkonvention basiert OutMassX(x); } // Ausgabe für Polygone: „richtigherum“, gleich mit Koordinatentransformation real poly_mx=0; // Verschiebung real poly_my=0; real poly_c=1; // Drehmatrix-Elemente | +cos +sin | real poly_s=0; // | -sin +cos | void SetPolyTr(real mx,real my,real angle/*in Grad*/) { angle*=PI/180; poly_mx=mx; poly_my=my; poly_c=cos(angle); poly_s=sin(angle); } void OutPolyXY(real x, real y) { int xx=poly_mx+poly_c*x-poly_s*y; int yy=poly_my+poly_s*x+poly_c*y; if (Flipped&4) {x=xx;xx=yy;yy=x;} OutMassX(xx); // In richtiger Reihenfolge ausgeben OutMassY(yy); } void OutPolyRA(real r, real a) { a*=PI/180; OutPolyXY(r*cos(a),r*sin(a)); } void OutRop(int NewRop) { if (NewRop==LayerRop) return; BeginRecord(0x0104/*META_SETROP2*/); OutWord(NewRop); EndRecord(); LayerRop=NewRop; } // die Füllstile könnten auch per CreatePatternBrush exakt angepasst werden // Da jedoch Schaltpläne äußerst selten Gebrauch davon machen, werden hier // nur die Standard-Windows-Füllstile verwendet. int FillStyles[]={ // bestmögliche Annäherung an Windows-Füllstile 1/*BS_NULL*/, // 0 Eagle-Füllung: 0/*BS_SOLID*/, // 1 0x02/*HS_HORIZONTAL*/, // 2 breite === 50% 0x32/*HS_BDIAGONAL*/, // 3 dünne /// 10% 0x32/*HS_BDIAGONAL*/, // 4 breite /// 50% 0x22/*HS_FDIAGONAL*/, // 5 breite \\\ 50% 0x22/*HS_FDIAGONAL*/, // 6 dünne \\\\ 10% 0x42/*HS_CROSS*/, // 7 dünne +++ 30% 0x52/*HS_DIAGCROSS*/, // 8 dünne xxx 30% 0x12/*HS_VERTICAL*/, // 9 Punkte 50% 0x12/*HS_VERTICAL*/, // A Punkte 10% 0x12/*HS_VERTICAL*/, // B Punkte 30% 0x02/*HS_HORIZONTAL*/, // C dünne === 50% 0x02/*HS_HORIZONTAL*/, // D dünne === 50% 0x02/*HS_HORIZONTAL*/, // E dünne === 50% 0x02/*HS_HORIZONTAL*/};// F dünne === 50% // Für genauere Füllstil-Simulation muss man entsprechende Bitmaps laden // Bei PostScript-Ausgabe werden unschön große Schraffuren ausgegeben int EaglePattern[]={ // ab Index 2 0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF, // breite waagerechte Linien 0x04,0x08,0x10,0x20,0x40,0x80,0x01,0x02, // HS_BDIAGONAL 0xE0,0xC1,0x83,0x07,0x0E,0x1C,0x38,0x70, // // dick 0x1C,0x0E,0x07,0x83,0xC1,0xE0,0x70,0x38, // \\ dick 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01, // HS_FDIAGONAL 0x11,0x11,0x11,0xFF,0x11,0x11,0x11,0xFF, // ++ 0x48,0x84,0x03,0x03,0x84,0x48,0x30,0x30, // xx 0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55, // Schachbrett 0x00,0x10,0x00,0x01,0x00,0x10,0x00,0x01, // Punkte dünn 0x00,0x44,0x00,0x11,0x00,0x44,0x00,0x11, // Punkte dichter 0x00,0xAA,0x00,0xAA,0x00,0xAA,0x00,0xAA, // Punkte angeordnet 0x00,0x55,0x00,0x55,0x00,0x55,0x00,0x55, 0x55,0x00,0x55,0x00,0x55,0x00,0x55,0x00, 0xAA,0x00,0xAA,0x00,0xAA,0x00,0xAA,0x00}; // Bei PostScript-Ausgabe erscheint eine grobe Klötzchenfüllung. // Umwandeln RGB in Windows-Farbe int rgb2bgr(int rgb) { return ((rgb&0xFF)<<16)|(rgb&0xFF00)|((rgb>>16)&0xFF); } int odd(int v) { while (v&~1) v=v&1^(v>>=1); return v; } // Füllstil-Code für Layer berechnen int CalcFill(int n) { int ret; switch (HatchType) { case 0: ret=0; break; case 1: { ret=FillStyles[fil[n]]; if (odd(Flipped&3)) switch (ret) { case 0x22/*HS_FDIAGONAL*/: case 0x32/*HS_BDIAGONAL*/: ret^=0x32^0x22; // Richtungen tauschen } }break; case 2: switch (fil[n]) { case 0: ret=1/*BS_NULL*/; break; case 1: ret=0/*BS_SOLID*/; break; default: ret=((fil[n]-2)<<4)|3; }break; } return ret; } // Funktion liefert 0 (FALSE) wenn Eagle-Layer unsichtbar int SetLayer(int n) { int c=col[n]; // c=0 wenn Layer nicht sichtbar if (c) { int rop=13/*R2_COPYPEN = P*/; if (Trans) switch (PaletteType) { case 0: case 2: rop=9/*R2_MASKPEN = DPa*/; break; case 1: rop=15/*R2_MERGEPEN = DPo*/; break; case 3: rop=3/*R2_MASKNOTPEN = DPna*/; break; // Komplementärfarben } OutRop(rop); // Für Schrift ist die ROP-Einstellung irrelevant! int rgb=PaletteType?palette(c,PaletteType-1):0; if (rop==3 && !rgb) rgb=0xFFFFFF; LayerColor=rgb2bgr(rgb); LayerFill=CalcFill(n); } return c; } // globale Variablen zur Verwaltung der sogenannten GDI-Objekte // Die Arrays wachsen mit der Verwendung verschiedener Pinsel, Stifte und Schriften. int OD_type[]; int OD_param1[]; int OD_param2[]; int OD_color[]; int FindObj(int type, int p1, int p2, int color) { int j; for (j=0; j=0) { BeginRecord(0x02FB/*META_CREATEFONTINDIRECT*/); OutWord(size); OutWord(0); OutWord(angle); OutWord(angle); // wird von Win16 ignoriert OutWord(FontBold[font]?700:400); OutWord(FontItalic[font]); OutWord(sel==4?0x200:0); // HIBYTE = LF_CHARSET OutWord(4/*OUT_TT_PRECIS*/); // sonst kleine Schriften immer 0° OutWord(0/*FontFamily[font]<<8*/); OutString(FontName[sel],1); EndRecord(); OutSelectObject(idx); } SetTextColor(color); if (TextAlign!=align) { BeginRecord(0x012E/*META_SETTEXTALIGN*/); OutWord(align); EndRecord(); TextAlign=align; } } // Koordinaten sortieren und zwei Punktkordinaten ausgeben void OutRect(int left, int top, int right, int bottom) { if (!(Flipped&1) && left>right || Flipped&1 && leftbottom) {int t=top; top=bottom; bottom=t;} OutXY(right,bottom); OutXY(left,top); // rclBox } // Gedrehtes Rechteck ausgeben void OutRectangle(int mx, int my, int x, int y, real angle) { int w=angle*10; x/=2; y/=2; // jetzt Abstand vom Ursprung switch (w) { case 0: case 900: case 1800: case 2700: { BeginRecord(0x041B/*META_RECTANGLE*/); if (w==0 || w==1800) { OutRect(mx-x,my-y,mx+x,my+y); }else{ // 90° / 270°: OutRect(mx-y,my-x,mx+y,my+x); } }break; default: { // Für Board: vollständige Koordinatentransformation und Ausgabe eines Polygons! BeginRecord(0x0324/*META_POLYGON*/); OutWord(4); SetPolyTr(mx,my,angle); OutPolyXY( x, y); // I. OutPolyXY(-x, y); // II. OutPolyXY(-x,-y); // III. OutPolyXY( x,-y); // IV. Quadrant } } EndRecord(); } // Gedrehtes RoundRect ausgeben (r!=0), nur für Board, SMDs void OutRoundRect(int mx, int my, int x, int y, int r, real angle) { int w=angle*10; x/=2; y/=2; // jetzt Abstand vom Ursprung switch (w) { case 0: case 900: case 1800: case 2700: { BeginRecord(0x061C/*META_ROUNDRECT*/); OutWord(Mass(2*r)); OutWord(Mass(2*r)); if (w==0 || w==1800) { OutRect(mx-x,my-y,mx+x,my+y); }else{ // 90° / 270°: OutRect(mx-y,my-x,mx+y,my+x); } }break; default: { // Polygonausgabe mit Kreisapproximation: als Zwölfeck (sollte reichen) real z=1-sin(PI/4); // 45° BeginRecord(0x0324/*META_POLYGON*/); OutWord(12); SetPolyTr(mx,my,angle); OutPolyXY( x , y-r ); OutPolyXY( x-r*z, y-r*z); OutPolyXY( x-r , y ); OutPolyXY(-x+r , y ); OutPolyXY(-x+r*z, y-r*z); OutPolyXY(-x , y-r ); OutPolyXY(-x ,-y+r ); OutPolyXY(-x+r*z,-y+r*z); OutPolyXY(-x+r ,-y ); OutPolyXY( x-r ,-y ); OutPolyXY( x-r*z,-y+r*z); OutPolyXY( x ,-y+r ); } } EndRecord(); } // Gedrehten(:-) Kreis ausgeben void OutCircle(int mx, int my, int r) { BeginRecord(0x0418/*META_ELLIPSE*/); OutRect(mx-r,my-r,mx+r,my+r); EndRecord(); } // Gedrehtes Achteck ausgeben (für Pads), type=0: normal, type=1: lang, type=2:exzentrisch void OutOctagon(int mx, int my, real d, real angle, int type) { d/=2; int i; real x[],y[]; real j=tan(22.5*PI/180); x[0]=d*j; y[0]=d; // Achteck im Uhrzeigersinn bauen x[1]=d; y[1]=d*j; x[2]=d; y[2]=-d*j; x[3]=d*j; y[3]=-d; x[4]=-d*j; y[4]=-d; x[5]=-d; y[5]=-d*j; x[6]=-d; y[6]=d*j; x[7]=-d*j; y[7]=d; if (type) for (i=0; i<4; i++) x[i]+=2*d; // lang machen (für Typ 1 und 2) if (type==1) for (i=0; i<8; i++) x[i]-=d; // zentrieren (für Typ 1) BeginRecord(0x0324/*META_POLYGON*/); OutWord(8); SetPolyTr(mx,my,angle); for (i=0; i<8; i++) OutPolyXY(x[i],y[i]); EndRecord(); } /* Abarbeitung von Eagle-Zeichenprimitiven */ int DontProcess(int ElemLayer) { if (PaletteType || board) return ElemLayer!=CurLayer; // Farbmodus else return !col[ElemLayer]; // Schwarzweiß-Modus } // wie SetBrush(), jedoch mit _einem_ Füllstil-Attribut void SetBrush2(int Fill) { SetBrush(Fill&0x0F,Fill>>4,LayerColor); } // Zum angegebenen Layer passenden Stift (minimale Strichbreite) erzeugen void SetLayerPen() { SetPen(0/*PS_SOLID*/,0,LayerColor); } // Zum angegebenen Layer passenden Pinsel erzeugen void SetLayerBrush() { SetBrush2(LayerFill); } void HandleCircle(UL_CIRCLE O) { if (DontProcess(O.layer)) return; SetPen(0/*PS_SOLID*/,O.width,LayerColor); if (O.width) SetBrush(1/*BS_NULL*/,0,0); else SetLayerBrush(); OutCircle(O.x,O.y,O.radius); } void HandleJunction(UL_JUNCTION O) { if (DontProcess(LAYER_NETS)) return; SetLayerPen(); SetLayerBrush(); OutCircle(O.x,O.y,O.diameter/2); } // Längliches Achteck anstelle Linie ausgeben (schraffierte Linien) // Keine Unterstützung für gestrichelte Linien! void OutOctLine(int x1, int y1, int x2, int y2, int d) { BeginRecord(0x0324/*META_POLYGON*/); d/=2; int i; real x[],y[]; real j=tan(22.5*PI/180); // Achteck-Ecken real c=x2-x1; // (c,s) = Richtungsvektor und real s=y2-y1; // zu real casten (Überlauf beim Quadrieren vermeiden) real l=sqrt(c*c+s*s); // Länge (Pythagoras) x[0]=l+d*j; y[0]=d; // Achteck nach rechts bauen x[1]=l+d; y[1]=d*j; x[2]=l+d; y[2]=-d*j; x[3]=l+d*j; y[3]=-d; x[4]=-d*j; y[4]=-d; x[5]=-d; y[5]=-d*j; x[6]=-d; y[6]=d*j; x[7]=-d*j; y[7]=d; OutWord(8); SetPolyTr(x1,y1,0); poly_c=c/l; // Hier: Mit möglicher Skalierung ohne Winkelangabe poly_s=s/l; for (i=0; i<8; i++) OutPolyXY(x[i],y[i]); EndRecord(); } void HandlePolygon(UL_POLYGON O) { // Polygone mit Kurven-Begrenzung können mit WMF nicht (oder höchstens // näherungsweise) unterstützt werden. if (DontProcess(O.layer)) return; int ExtraBorder=LayerFill && LineReplace>=2 && u2mic(O.width)>=MinBreite; if (ExtraBorder) SetLayerPen(); // dünner Rand else SetPen(0/*PS_SOLID*/,O.width,LayerColor); if (O.pour==POLYGON_POUR_HATCH) SetBrush(2,4/*HS_CROSS*/,LayerColor); else{ int Fill=0; if (board) Fill=LayerFill; SetBrush2(Fill); } int np=0,nk[],n=0,x0,y0; nk[0]=0; O.contours(W) { // Hoppla! "contours" macht aus Bögen Geradenstücke, auch im Schaltplan! // Das erspart hier eine Menge Arbeit. if (!nk[np]) x0=W.x1, y0=W.y1; // Linienzug-Anfang nk[np]++; // Kanten abzählen (per Polygon) n++; // Kanten abzählen (Gesamtanzahl) if (x0==W.x2 && y0==W.y2) nk[++np]=0; // Linienzug-Ende am Anfang } // if (!first) return; // Ende != Anfang (sollte in Eagle nicht vorkommen) // if (nk[0]<3) return; // Entartetes Polygon (sollte auch nie vorkommen) if (np==1) { BeginRecord(0x0324/*META_POLYGON*/); OutWord(n); // cptl }else{ BeginRecord(0x0538/*META_POLYPOLYGON*/); OutWord(np); // Anzahl Einzelpolygone for (n=0; n0; b-=45) { OutPolyRA(rad,b); } r+=O.width; // äußerer Bogen (Uhrzeiger) SetPolyTr(O.xc,O.yc,0); OutPolyRA(r,O.angle2); // Endpunkt außen do{ a-=INCREMENT; // Stützpunkte gegenüberstehend! OutPolyRA(r,a); }while(--i); SetPolyTr(O.x1,O.y1,O.angle1); if (O.cap!=CAP_FLAT) for (b=-22.5; b>-180; b-=45) { OutPolyRA(rad,b); } EndRecord(); } void HandleWire(UL_WIRE O) { if (DontProcess(O.layer)) return; int st; switch (O.style) { case WIRE_STYLE_CONTINUOUS: st=0/*PS_SOLID*/; break; case WIRE_STYLE_LONGDASH: st=1/*PS_DASH*/; break; case WIRE_STYLE_SHORTDASH: st=2/*PS_DOT*/; break; case WIRE_STYLE_DASHDOT: st=3/*PS_DASHDOT*/; break; } if (O.arc) { // Die Ausgabe von Bögen ist bei großen Strichbreiten unbefriedigend, // weil bei Eagle glatte Kurvenenden gezeichnet werden (PS_ENDCAP_FLAT); // WMF (Windows 3.1) unterstützt nur PS_ENDCAP_ROUND. // In diesem Fall ist ein „Tortenstück“ (Pie) die bessere Ausgabe. // TODO: // Die Bogenausgabe für gestrichelte Layer erfordert // Polygonausgabe mit einer großen Anzahl Stützstellen. // Das ENDCAP-Problem ist hierbei keins. int rad; if (IsPie(O.arc)) { SetLayerPen(); SetLayerBrush(); BeginRecord(0x081A/*META_PIE*/); rad=O.arc.radius+O.width/2; }else if (u2mic(O.width)>=MinBreite && ( O.arc.cap==CAP_FLAT && O.arc.width>=FlatArcReplace*O.arc.radius || LayerFill && LineReplace)) { SetLayerPen(); SetLayerBrush(); if (PiecedReplace && O.style!=WIRE_STYLE_CONTINUOUS) O.pieces(W) OutPolyArc(W.arc); else OutPolyArc(O.arc); return; }else{ SetPen(st,O.width,LayerColor); BeginRecord(0x0817/*META_ARC*/); rad=O.arc.radius; } if (odd(Flipped&7)) { OutXY(O.arc.x1,O.arc.y1); OutXY(O.arc.x2,O.arc.y2); }else{ OutXY(O.arc.x2,O.arc.y2); OutXY(O.arc.x1,O.arc.y1); } OutRect(O.arc.xc-rad,O.arc.yc-rad, O.arc.xc+rad,O.arc.yc+rad); EndRecord(); }else{ if (LayerFill && LineReplace && u2mic(O.width)>=MinBreite) { SetLayerPen(); SetLayerBrush(); if (PiecedReplace && O.style!=WIRE_STYLE_CONTINUOUS) O.pieces(W) OutOctLine(W.x1,W.y1,W.x2,W.y2,W.width); else OutOctLine(O.x1,O.y1,O.x2,O.y2,O.width); }else if (PiecedReplace>=2 && O.style!=WIRE_STYLE_CONTINUOUS && u2mic(O.width)>=MinBreite) { SetPen(0,O.width,LayerColor); O.pieces(W) OutLine(W.x1,W.y1,W.x2,W.y2); }else{ SetPen(st,O.width,LayerColor); OutLine(O.x1,O.y1,O.x2,O.y2); } } } // Textfarbe dunkler machen (auffälliger bei transparenter Ausgabe) int DarkerColor(int col) { int rgb[]; rgb[0]=col&0xFF; rgb[1]=(col>>8)&0xFF; rgb[2]=(col>>16)&0xFF; rgb[0]>>=2; // vierteln rgb[1]>>=2; rgb[2]>>=2; return (rgb[2]<<16)|(rgb[1]<<8)|rgb[0]; } string CharWidth[]={ "8<>OOlY0<Ou9uK`7HO;`JAN99;PF599;H___E]]]]]]iXSSSS<<<<]]]]]]]O]]]]]]OJEEEEEEXEEEEE7777JJJJJJJNJJJJJJJJ"}; int GetVectorLineWidth(UL_TEXT O) { O.wires(W) return W.width; } // Entfernt Ausrufezeichen sowie Escape-Rückwärtsstriche, // malt die Überstreichungs-Striche möglichst genau über die (proportionalen, // True-Type-) Buchstaben in der Linienbreite der alternativen Vektorschrift string HandleNegation(UL_TEXT O, real angle, int textalign) { if (!DoNegate) return O.value; string s=O.value; int i,j,n; // n gerade wenn kein Strich int sel=FontSel[O.font]; // muss !=0 sein! real mult=O.size*1.6/96; real x,xx[],y; // laufende Pixel, Pixel für Linie, Höhe char c; // Modifizieren des Strings, Finden der Überstreichpositionen for (i=j=x=n=0; c=s[i]; i++) { switch (c) { case '\\': { c=s[++i]; // Folgezeichen buchstäblich nehmen if (!c) { --i; continue; // Backslash herausnehmen } }break; case '!': if (!(n&1)) { switch (s[i+1]) { // Folgezeichen untersuchen case 0: case ' ': case '!': case '\'': case '"': case ')': case ']': case '}': break; default: n++; } if (n&1) { xx[n-1]=x; // Startposition vermerken continue; // Ausrufezeichen herausnehmen } }else{ xx[n++]=x; // Endposition vermerken continue; // Ausrufezeichen herausnehmen }break; case ',': if (n&1) { xx[n++]=x; // Endposition vermerken }break; } s[j++]=c; if (sel==2) x+=53*mult; // Zeichenbreite für 96 Punkt "Courier New" else if (c<32) x+=40*mult; // falls Sonderzeichen auftauchen (falsches UTF-8) else x+=(CharWidth[sel-1][c-32]-32)*mult; // Stringbreite in Pixel mitschneiden } if (n&1) xx[n++]=x; s[j]=0; // Ziel (nie länger als Quelle) abhacken if (n) { // Striche malen angle*=PI/180; real c=cos(angle); // Drehmatrix-Elemente | +cos +sin | real s=sin(angle); // | -sin +cos | real x0=O.x; real y0=O.y; if (textalign&2/*TA_RIGHT*/) { x0-=x*c; y0-=x*s; } if (textalign&24/*TA_BASELINE*/) y=O.size*1.25; // empirischer Abstand else y=O.size*0.25; x0-=y*s; y0+=y*c; SetPen(0/*PS_SOLID*/,GetVectorLineWidth(O),LayerColor); for (i=0; iZeichen werden falsch wiedergegeben."); real w=O.angle; int a; switch (O.align) { case 0/*ALIGN_BOTTOM_LEFT*/ : a=0x08; break; // TA_LEFT|TA_BOTTOM case 1/*ALIGN_BOTTOM_CENTER*/: a=0x0E; break; // TA_CENTER|TA_BOTTOM case 2/*ALIGN_BOTTOM_RIGHT*/ : a=0x0A; break; // TA_RIGHT|TA_BOTTOM case 3/*ALIGN_CENTER_LEFT*/ : a=0x18; break; // TA_LEFT|TA_BASELINE case 4/*ALIGN_CENTER*/ : a=0x1E; break; // TA_CENTER|TA_BASELINE case 5/*ALIGN_CENTER_RIGHT*/ : a=0x1A; break; // TA_RIGHT|TA_BASELINE case 6/*ALIGN_TOP_LEFT*/ : a=0x00; break; // TA_LEFT|TA_TOP case 7/*ALIGN_TOP_CENTER*/ : a=0x06; break; // TA_CENTER|TA_TOP case 8/*ALIGN_TOP_RIGHT*/ : a=0x02; break; // TA_RIGHT|TA_TOP } // Nur für Schaltplan: if (schematic && O.mirror) { if (w==90 || w==270) a^=8; // TA_TOP <-> TA_BOTTOM else a^=2; // TA_LEFT <-> TA_RIGHT } if (!O.spin && w>90 && w<=270) { // Änderung von Ausrichtung bei spin=0? w-=180; if (w<0) w+=360; // (für Ausführung im Board) a^=8^2; // Ausrichtung genau anders herum } if (Debug) OutCircle(O.x,O.y,mil2u(20)); // Radius: 20 mil = 0,5 mm real dy=0; // Y-Verschiebung if (a&16) dy=-0.5; // vertikale Zentrierung? Halbe Schriftgröße nach unten else if (!(a&8)) dy=0.28; // TA_TOP? Etwas nach oben korrigieren int mx=O.x-O.size*Sin(dy,w); int my=O.y+O.size*Cos(dy,w); if (Flipped&2) w+=180; // huh? Reicht das? if (Flipped&4) w+=90; string s=HandleNegation(O,w,a); int l=strlen(s); if (!l) return; int col=LayerColor; if (Trans && PaletteType==3 && CurLayer>=21) col=DarkerColor(col); if (a&0x04) a|=0x06; // Alignment-Wert für Windows korrigieren if (a&0x18) a|=0x18; // Aus TA_BOTTOM stets TA_BASELINE machen if (O.font==FONT_VECTOR && HasSymbolChars(s)) { a|=1/*TA_UPDATECP*/; OutMoveTo(mx,my); string s1; int pos=0, sublen; int font=O.font; if (a&2) s=strrev(s); // TA_RIGHT for(;;) { s1=GetCharRun(s,pos,font&4); sublen=strlen(s1); if (pos && !sublen) break; // abbrechen bei Ende if (sublen) { if (a&2) s1=strrev(s1); // TA_RIGHT SetFont(font,O.size,w*10,col,a); BeginRecord(0x0521/*META_TEXTOUT*/); OutWord(sublen); OutString(s1,0); OutDword(0); // Position hier nicht ausgewertet! // Ohne diese Null hat der F3-Viewer von Total Commander ein ernstes Problem! // Ansonsten kann man diese auch weglassen. EndRecord(); } font^=4; // umschalten Symbol<->ANSI-Schriftart pos+=sublen; } LastX=INT_MAX; // Position ungültig return; } SetFont(O.font,O.size,w*10,col,a); BeginRecord(0x0521/*META_TEXTOUT*/); OutWord(l); OutString(s,0); OutXY(mx,my); EndRecord(); } /* Nur für Schaltplan */ string DirNames[]={"Nc","In","Out","Io","Oc","Pwr","Pas","Hiz","Sup"}; void HandlePin(UL_PIN O) { if (DontProcess(LAYER_PINS)) return; SetLayerPen(); SetBrush(1/*BS_NULL*/,0,0); OutCircle(O.x,O.y,mic2u(1016)); int w=O.angle; int a=8|2/*TA_BOTTOM|TA_RIGHT*/; if (w>=180) { w-=180; a^=2/*TA_RIGHT*/; } SetFont(1/*Proportional*/,mic2u(950),w*10,LayerColor,a); string s; BeginRecord(0x0521/*META_TEXTOUT*/); OutWord(sprintf(s,"%s %d",DirNames[O.direction],O.swaplevel)); OutString(s,0); OutXY(O.x,O.y); EndRecord(); } /* Nur für Boards: */ void OutDrillSymbol(int x, int y, int type) { int D=mil2u(40); switch (type) { case 0: OutLine(x-D,y-D,x+D,y+D); OutCircle(x,y,D); break; // Ø case 1: OutLine(x-D,y,x+D,y); OutLine(x,y-D,x,y+D); break; // + case 2: OutLine(x-D,y-D,x+D,y+D); OutLine(x-D,y+D,x+D,y-D); break; // x case 3: OutLine(x,y,x,y+D); OutRectangle(x,y,D*2,D*2,0); break; case 4: OutLine(x,y,x,y+D); OutLineTo(x-D,y); OutLineTo(x,y-D); OutLineTo(x+D,y); OutLineTo(x,y+D); break; case 5: OutLine(x-D,y-D,x+D,y+D); OutLineTo(x-D,y+D); OutLineTo(x+D,y-D); OutLineTo(x-D,y-D); break; case 6: OutLine(x-D,y+D,x+D,y-D); OutLineTo(x+D,y+D); OutLineTo(x-D,y-D); OutLineTo(x-D,y+D); break; case 7: OutLine(x-D,y,x+D,y); OutLineTo(x,y+D); OutLineTo(x,y-D); OutLineTo(x-D,y); break; case 8: OutLine(x-D,y,x+D,y); OutLineTo(x,y-D); OutLineTo(x,y+D); OutLineTo(x-D,y); break; case 9: OutLine(x-D,y+D,x+D,y-D); OutLine(x,y-D,x,y+D); OutLine(x+D,y+D,x-D,y-D); break; case 10: OutLine(x-D,y,x+D,y); OutLine(x+D,y+D,x-D,y-D); OutLine(x-D,y+D,x+D,y-D); break; case 11: OutLine(x,y-D,x,y); OutLineTo(x+D,y+D); OutLineTo(x-D,y+D); OutLineTo(x,y); break; case 12: OutLine(x,y+D,x,y); OutLineTo(x+D,y-D); OutLineTo(x-D,y-D); OutLineTo(x,y); break; case 13: OutLine(x-D,y,x,y); OutLineTo(x+D,y+D); OutLineTo(x+D,y-D); OutLineTo(x,y); break; case 14: OutLine(x+D,y,x,y); OutLineTo(x-D,y-D); OutLineTo(x-D,y+D); OutLineTo(x,y); break; case 16: OutCircle(x,y,D/2); /*nobreak;*/ case 15: OutLine(x-D,y,x+D,y); OutLine(x,y+D,x,y-D); OutCircle(x,y,D); break; case 18: OutRectangle(x,y,D,D,0); /*nobreak;*/ case 17: OutLine(x-D,y,x+D,y); OutLine(x,y+D,x,y-D); OutRectangle(x,y,D*2,D*2,0); break; } } // Die Wärmefallen im Supply-Layer werden wesentlich von den Entwurfsregeln bestimmt. // Da ist aus ULP-Sicht IMHO kein Zugriff möglich. // Daher sind hier die Wärmefallen nur Demo und nicht produktionstauglich. void OutThermal(int x, int y, int r) { OutOctagon(x-r,y,r>>2,90,1); OutOctagon(x+r,y,r>>2,90,1); OutOctagon(x,y-r,r>>2,0,1); OutOctagon(x,y+r,r>>2,0,1); } void DrawHole(int x, int y, int d) { if (DrawHoles) { SetPen(5/*PS_NULL*/,0,0); SetBrush(0/*BS_SOLID*/,0,BackgroundColor); OutCircle(x,y,d/2); } } int SupplyLayer() { return 2<=CurLayer && CurLayer<=15 && nam[CurLayer][0]=='$'; } // ermittelt Bezug zum zu verwendenden Layer für PADs // Liefert 0 für "don't process" int GetPadLayer(int flags) { if (SupplyLayer()) return CurLayer; switch (CurLayer) { case LAYER_TSTOP: case LAYER_BSTOP: if (flags&PAD_FLAG_STOP) return CurLayer; break; case LAYER_PADS: return LAYER_TOP; } return 0; } void HandlePad(UL_PAD O) { if (!O) return; if (CurLayer==LAYER_DRILLS) { SetPen(0/*PS_SOLID*/,3*254,LayerColor); SetBrush(1/*BS_NULL*/,0,0); OutDrillSymbol(O.x,O.y,O.drillsymbol); return; } int d=0, PadLayer=GetPadLayer(O.flags); if (!PadLayer) return; d=O.diameter[PadLayer]; if (!d) return; // raus wenn null geblieben if (!DrawHoles) { SetLayerPen(); SetLayerBrush(); switch (O.shape[PadLayer]) { // im LAYER_PADS ist O.shape immer PAD_SHAPE_ROUND, dann von LAYER_TOP nehmen case PAD_SHAPE_SQUARE: OutRectangle(O.x,O.y,d,d,O.angle); break; // hier künftig ARC ausgeben! (aber nur, wenn Layer SOLID) case PAD_SHAPE_ROUND: OutCircle(O.x,O.y,d/2); break; case PAD_SHAPE_OCTAGON: OutOctagon(O.x,O.y,d,O.angle,0); break; case PAD_SHAPE_LONG: OutOctagon(O.x,O.y,d,O.angle,1); break; case PAD_SHAPE_OFFSET: OutOctagon(O.x,O.y,d,O.angle,2); break; case PAD_SHAPE_ANNULUS: OutCircle(O.x,O.y,d/2); break; case PAD_SHAPE_THERMAL: OutThermal(O.x,O.y,d/2); break; default: dlgMessageBox("??"); } } if (PadLayer==LAYER_TOP) DrawHole(O.x,O.y,O.drill); } // ermittelt Bezug zum zu verwendenden Layer für PADs // Liefert 0 für "don't process" int GetViaLayer(int flags) { if (SupplyLayer()) return CurLayer; switch (CurLayer) { case LAYER_TSTOP: case LAYER_BSTOP: if (flags&VIA_FLAG_STOP) return CurLayer; break; case LAYER_VIAS: return LAYER_TOP; } return 0; } void HandleVia(UL_VIA O) { if (!O) return; if (CurLayer==LAYER_DRILLS) { SetPen(0/*PS_SOLID*/,3*254,LayerColor); SetBrush(1/*BS_NULL*/,0,0); OutDrillSymbol(O.x,O.y,O.drillsymbol); return; } int d=0, ViaLayer=GetViaLayer(O.flags); if (!ViaLayer) return; d=O.diameter[ViaLayer]; if (!d) return; // raus wenn null geblieben if (!DrawHoles) { SetLayerPen(); SetLayerBrush(); switch (O.shape[ViaLayer]) { case VIA_SHAPE_SQUARE: OutRectangle(O.x,O.y,d,d,0); break; // hier künftig ARC ausgeben! (aber nur, wenn Layer SOLID) case VIA_SHAPE_ROUND: OutCircle(O.x,O.y,d/2); break; case VIA_SHAPE_OCTAGON: OutOctagon(O.x,O.y,d,0,0); break; case VIA_SHAPE_ANNULUS: OutCircle(O.x,O.y,d/2); break; case VIA_SHAPE_THERMAL: OutThermal(O.x,O.y,d/2); break; default: dlgMessageBox("???"); } } if (ViaLayer==LAYER_TOP) DrawHole(O.x,O.y,O.drill); } int GetSmdLayer(int SmdLayer, int flags) { switch (SmdLayer) { case LAYER_BOTTOM: switch (CurLayer) { // SMD-Pad unten case LAYER_BSTOP: if (flags&SMD_FLAG_STOP) return LAYER_BSTOP; break; case LAYER_BCREAM: if (flags&SMD_FLAG_CREAM) return LAYER_BCREAM; break; case LAYER_BOTTOM: return LAYER_BOTTOM; }break; case LAYER_TOP: switch (CurLayer) { // SMD-Pad oben case LAYER_TSTOP: if (flags&SMD_FLAG_STOP) return LAYER_TSTOP; break; case LAYER_TCREAM: if (flags&SMD_FLAG_CREAM) return LAYER_TCREAM; break; case LAYER_TOP: return LAYER_TOP; }break; } return 0; } void HandleSmd(UL_SMD O) { if (!O) return; int SmdLayer=GetSmdLayer(O.layer,O.flags); if (!SmdLayer) return; int dx=O.dx[SmdLayer], dy=O.dy[SmdLayer]; if (!dx) return; // raus wenn null geblieben SetLayerPen(); SetLayerBrush(); if (O.roundness) { // Angabe in Prozent der kürzeren Seite if (O.roundness==100 && dx==dy) OutCircle(O.x,O.y,dx/2); else{ int r=dx=0; CurLayer--) { if (SetLayer(CurLayer)) SchWalkLayer(O); }else SchWalkLayer(O); // Schwarzweiß NICHT im Sandwich-Betrieb abarbeiten (schneller) } void BrdWalkLayer2(UL_BOARD O, int DoLast) { if (!SetLayer(CurLayer)) return; if (!DoLast && strchr(LastLayers,CurLayer)>=0) return; // überspringen BrdWalkLayer(O); // Zweiter Durchlauf für die Löcher (In einem einzelnen Durchlauf würde, // wegen der vielen Kontextumschaltungen, die .WMF-Datei viel größer werden.) if (CurLayer==LAYER_VIAS || CurLayer==LAYER_PADS) { DrawHoles=1; OutRop(13/*R2_COPYPEN*/); // Löcher zum Hintergrund "durchstanzen" BrdWalkLayer(O); DrawHoles=0; } } /* Abarbeitung layer-weise, sogar wenn schwarz-weiß */ void BrdWalkLayers(UL_BOARD O) { // Eagle gibt die Kupferlagen von unten nach oben aus if (Trans) { // Löcher ausstanzen lassen, bevor Kupferlinien hineingezogen werden (wie Eagle) for (CurLayer=17; CurLayer<21; CurLayer++) BrdWalkLayer2(O,0); for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(O,0); }else{ for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(O,0); for (CurLayer=17; CurLayer<21; CurLayer++) BrdWalkLayer2(O,0); } for (CurLayer=29; CurLayer<51; CurLayer++) BrdWalkLayer2(O,0); for (CurLayer=53; CurLayer<256; CurLayer++) BrdWalkLayer2(O,0); //Typische Text-Layer zuletzt for (CurLayer=51; CurLayer<53; CurLayer++) BrdWalkLayer2(O,0); for (CurLayer=21; CurLayer<29; CurLayer++) BrdWalkLayer2(O,0); Trans=0; // Transparenz für LastLayers ausschalten for (int i=0; CurLayer=LastLayers[i]; i++) BrdWalkLayer2(O,1); } int Area[]; // Bereich (X1,Y1,X2,Y2; sortiert), in WMF-Einheiten void FatalExit(string s) { dlgMessageBox(s); exit(EXIT_FAILURE); } void FatalExit1(string format, string info) { string s; sprintf(s,format,info); FatalExit(s); } void OutStart(UL_AREA O) { Area[0]=Mass(O.x1); Area[1]=Mass(O.y1); Area[2]=Mass(O.x2); Area[3]=Mass(O.y2); if (Flipped&4) {int t=Area[0]; Area[0]=Area[1]; Area[1]=t; t=Area[2]; Area[2]=Area[3]; Area[3]=t;} //X<->Y tauschen (zuerst!) if (!(Flipped&2)) {int t=Area[1]; Area[1]=-Area[3]; Area[3]=-t;} // Y tauschen und Vorzeichenwechsel (WMF zählt von unten) if (Flipped&1) {int t=Area[0]; Area[0]=-Area[2]; Area[2]=-t;} // X tauschen und Vorzeichenwechsel if (max(Area[2]-Area[0],Area[3]-Area[1])>32767) // Notbremse! Dateileiche bleibt liegen. FatalExit("Teiler zu klein; resultierende Abmessungen übersteigen 16 bit!"); nWords=0; // Kopf beginnen nMaxRecSize=0; ClearObjects(); // Normalen Windows-Metafile-Kopf ausgeben OutWord(2); // FileType = Disk OutWord(9); // HeaderSize OutWord(0x0100); // Version nWords=8; // noch unbekannte WORDs überspringen OutWord(0); // NumOfParams // Ohne die 3 folgenden Records haben Total Commander sowie Internet Explorer ein Problem! BeginRecord(0x0103/*META_SETMAPMODE*/); OutWord(8/*MM_ANISOTROPIC*/); EndRecord(); BeginRecord(0x020B/*META_SETWINDOWORG*/); OutWord(Area[1]); OutWord(Area[0]); EndRecord(); BeginRecord(0x020C/*META_SETWINDOWEXT*/); OutWord(Area[3]-Area[1]+1); OutWord(Area[2]-Area[0]+1); EndRecord(); BeginRecord(0x0102/*META_SETBKMODE*/); OutWord(1/*TRANSPARENT*/); // für Schrift und normale Schraffur EndRecord(); if (HatchType==2) { BeginRecord(0x0201/*META_SETBKCOLOR*/); int col=BackgroundColor; if (Trans && PaletteType==3) col^=0xFFFFFF; OutDword(col); // für Pattern-Schraffur (ist immer deckend - leider!) EndRecord(); } if (OutBkgnd) { SetPen(5/*PS_NULL*/,0,0); // Hintergrundrechteck mit leichtem Übermaß zeichnen SetBrush(0/*BS_SOLID*/,0,BackgroundColor); BeginRecord(0x041B/*META_RECTANGLE*/); int o=mil2u(10); OutRect(O.x1-o,O.y1-o,O.x2+o,O.y2+o); EndRecord(); } } void OutWrite() { int PHeader[]; // Vorhaltung zur Berechnung der Prüfsumme int i; BeginRecord(0/*META_EOF*/); EndRecord(); status("Datei-Ausgabe ("+FName+")"); // Aldus-Placeable-Metafile-Kopf ausgeben PHeader[0]=0xCDD7; // DWORD Key PHeader[1]=0x9AC6; /*0*/ // WORD Handle PHeader[3]=Area[0]; // SHORT Left PHeader[4]=Area[1]; // SHORT Top PHeader[5]=Area[2]+1; // SHORT Right PHeader[6]=Area[3]+1; // SHORT Bottom // Manche Programme interpretieren diese Grenzen als inklusive, // manche andere als exklusive unteren rechten Rand. // Das +1 vermeidet Abschneideprobleme. PHeader[7]=round(25400/Teiler/Scale); // ergibt 1:1-Import in Word // Beim Standard-Teiler von 25,4 µm/Pixel ergibt das 1000 dpi // Eagle <6 hat 254000 dpi, also 256 dot per mil und 10 dot per mm // Eagle 6 hat 8128000 dpi, das 32-fache von Eagle <6 // Die Angaben im SDK scheinen allesamt irreführend zu sein! // Richtig (und auch logisch) scheint: WMF-Einheiten ergeben 1 Zoll. // Schaltpläne sehen damit im IE6 (und .CHM-Dateien) unansehnlich aus. /*0,0*/ // DWORD Reserved /*0*/ // WORD Checksum for (i=0; i<10; i++) PHeader[10]^=PHeader[i]; // Prüfsumme ermitteln for (i=0; i<11; i++) PrintWord(PHeader[i]); // Windows-Metafile-Kopfdaten ergänzen FileData[3]=nWords&0xFFFF; // FileSize FileData[4]=nWords>>16; FileData[5]=nHandles; // NumOfObjects FileData[6]=nMaxRecSize&0xFFFF;// MaxRecordSize FileData[7]=nMaxRecSize>>16; for (i=0; i=0) { suffix=strsub(val,p); val=strsub(val,0,p); } if (val=="") ; // ohne Schriftname Voreinstellung lassen else if (val=="Vector" || val=="V") FontSel[idx]=0; else if (val=="Arial" || val=="A") FontSel[idx]=1; else if (val=="Courier" || val=="C") FontSel[idx]=2; else if (val=="Times" || val=="T") FontSel[idx]=3; else FatalExit1("Unbekannte Schriftartbezeichnung %s!",val); FontBold[idx]=0; FontItalic[idx]=0; for (p=0; p oder =. // Nur ein ist erlaubt, der kein '=' enthalten darf. // Alle im Dialog einstellbaren Variablennamen sind erlaubt. // Der Dateiname "*" spezifiziert den Standard-Dateinamen. void ParseCommandLine(void) { int i,j,p; string arg,key,val; for (i=1; i=0) { // normales Argument key=strsub(arg,0,p); val=strsub(arg,p+1); if (key=="Debug") Debug=strtol(val); else if (key=="WMF-Unit") Teiler=strtod(val); else if (key=="MinWidth") MinBreite=strtod(val); else if (key=="PaletteType" || key=="p") { PaletteType=strtol(val); OnSetPaletteType(); }else if (key=="HatchType" || key=="h") HatchType=strtol(val); else if ((key=="AllPages" || key=="a") && schematic) AllPages=strtol(val); else if ((key=="Flipped" || key=="f") && board) Flipped=strtol(val); else if (key=="Trans" || key=="t") Trans=strtol(val); else if (key=="OutBkgnd" || key=="b") OutBkgnd=strtol(val); else if (key=="DoNegate" || key=="n") DoNegate=strtol(val); else if (key=="LineReplace" || key=="l") LineReplace=strtol(val); else if (key=="FlatArcReplace" || key=="c") FlatArcReplace=strtod(val); else if (key=="PiecedReplace") PiecedReplace=strtol(val); else if (key=="Dialog" || key=="d") Dialog=strtol(val); else if ((key=="LastLayers" || key=="y") && board) LastLayers=val; else if (key=="Vector" || key=="V") ParseFont(0,val); else if (key=="Proportional" || key=="P") ParseFont(1,val); else if (key=="Fixed" || key=="F") ParseFont(2,val); else if ((key=="CheckRipupPoly" || key=="r") && board) CheckRipupPoly=strtol(val); else if (key=="Group" || key=="g") GroupOnly=strtol(val); else if (key=="Scale" || key=="s") Scale=strtod(val); else FatalExit1("Unbekannte Variable %s!",key); }else{ // Dateiname if (FName) FatalExit1("Mehrere Dateinamen (%s) sind nicht möglich!",arg); if (arg=="*") FName=AutomaticFileName(); else{ FName=arg; if (FName[0]!='\\' && FName[0]!='/' && FName[1]!=':') // Relative Pfade sollen sich aufs Schaltplan- bzw. Board-Verzeichnis beziehen. // Ohne diese Aktion wäre es relativ zum Eagle/bin-Verzeichnis. FName=filedir(GetWorkFileName())+FName; } } } if (!Dialog && !CalcLastLayers()) exit(EXIT_FAILURE); } /* Hauptprogramm */ if (!schematic && !board) { FatalExit("Muss aus Schaltplan oder Board gestartet werden!"); } // Informationen über Schaltplan und Layer einsammeln if (schematic) { schematic(S) { S.layers(L) if (L.visible) { col[L.number]=L.color; // Layer vermerken fil[L.number]=L.fill; // Füll-Stile (1 = komplett gefüllt) nam[L.number]=L.name; // Name (für Status-Anzeige) if (L.fill!=1) NumHatched++; } SName=filename(S.name); SchCountFonts(S); } HatchType=0; Scale=1.3888888; // ausprobiert für gute 1:1-Bildschirmdarstellung, bspw. im IE6 // Beim Standard-Teiler von 25,4 µm/Pixel ergibt das 720 dpi }else board(B) { B.layers(L) if (L.visible) { col[L.number]=L.color; // Layer vermerken fil[L.number]=L.fill; // Füll-Stile (1 = komplett gefüllt) nam[L.number]=L.name; // Name (für Status-Anzeige und Supply-Layer) if (L.fill!=1) NumHatched++; if (L.fill==2 || L.fill==4 || L.fill==5 || L.fill>=9) NumExact++; } SName=filename(B.name); BrdCountFonts(B); CheckRipupPoly=1; // diese Layer sind wesentliches Kriterium zur Voreinstellung Flipped=col[LAYER_BPLACE] && !col[LAYER_TPLACE]; } if (NumExact) HatchType=2; // Vorgabe wenn solche Schraffuren sichtbar (nur Board) OnSetPaletteType(); // Kommandozeile auswerten ParseCommandLine(); if (CheckRipupPoly) board(B) if (BrdAreRipupPolys(B)) switch (dlgMessageBox( "!Es sind Signal-Polygone vorhanden, die nur als Außenkontur "+ "(nicht freigerechnet) erscheinen.\n"+ "Für korrekte Polygondarstellung vorher RATSNEST ausführen!", "&RATSNEST starten","Polygone &ignorieren", "&Abbrechen")) { case 0: exit("RATSNEST;run "+strjoin(argv,' ')); break; case 2: exit(0); break; } // Nutzer-Dialog anzeigen Rotated=Flipped>>2&1; TopDown=Flipped>>1&1; Flipped&=1; if (Dialog && DialogBox()!=1) exit(EXIT_FAILURE); if (TopDown) Flipped|=2; if (Rotated) Flipped^=5; // Rotation nach links (nach Spiegelung) // Hintergrundfarbe (für Löcher!) einstellen if (!PaletteType || PaletteType==3 && Trans) BackgroundColor=0xFFFFFF; else BackgroundColor=rgb2bgr(palette(0,PaletteType-1)); // Dateiname erfragen; Vorgabe für Boards ist Suffix "t" (TOP) oder "b" (BOTTOM) if (!FName) { FName=AutomaticFileName(); FName=dlgFileSave("Speichern Windows-Metadatei",FName,"*.wmf"); } if (!FName) exit(EXIT_FAILURE); if (AllPages) { schematic(O) O.sheets(O) { string e; sprintf(e,"-%d%s",O.number,fileext(FName)); output(filesetext(FName,e),"wb") SchPrintMF(O); } }else{ if (board) board(O) { output(FName,"wb") BrdPrintMF(O); }else sheet(O) { output(FName,"wb") SchPrintMF(O); } }