TechDraw TemplateGenerator/de: Difference between revisions

From FreeCAD Documentation
(Created page with "Jeder Texteditor kann zum Erstellen von Kode genutzt werden. Mein Favorit ist Atom... Aber FreeCADs eingebauter Editor funktioniert auch bestens.")
No edit summary
 
(108 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{UnfinishedDocu{{#translation:}}}}
<languages/>
<languages/>


{{TutorialInfo/de
{{TutorialInfo/de
|Topic= TechDraw-Vorlagen erzeugen mittels Python-Macro
|Topic=Eine TechDraw-Vorlagen unter Verwendung eines Python-Makros erstellen
|Level= Grundkenntnisse von Python und SVG-Strukturen sind hilfreich
|Level=Grundkenntnisse von Python und SVG-Strukturen sind hilfreich
|FCVersion= 0.19.1 und later
|FCVersion= 0.21 und neuer
|Time= (noch nicht bekannt)
|Time=(noch nicht bekannt)
|Author= [[User:FBXL5]]
|Author=[[User:FBXL5|FBXL5]]
|SeeAlso=[[Macro_TemplateHelper/de|Makro TemplateHelper]]
}}
}}


<span id="Introduction"></span>
== Einleitung ==
== Einleitung ==


Diese Anleitung beschreibt, wie man eine einfache Vorlage zur Benutzung mit dem TechDraw-Arbeitsbereich aus ein paar Zeilen Python-Kode erstellt.
Diese Anleitung beschreibt, wie man eine einfache Vorlage zur Benutzung mit dem TechDraw-Arbeitsbereich aus ein paar Zeilen Python-Kode erstellt.


Jeder Texteditor kann zum Erstellen von Kode genutzt werden. Mein Favorit ist Atom... Aber FreeCADs eingebauter Editor funktioniert auch bestens.
Jeder Texteditor kann zum Erstellen von Kode genutzt werden. Mein Favorit ist Atom, aber FreeCADs eingebauter Editor funktioniert auch bestens.


Die folgenden Kode-Beispiele können kopiert, in eine leere Textdatei eingefügt und dann unter einem selbstgewählten Namen als *.py oder *.FCMacro abgespeichert werden.
The following code examples can be copied and pasted into an empty text file and then saved under a name of your choice as a *.py or *.FCMacro file.


Eine Vorlage stellt einen Hintergrund für Zeichnungsaufgaben bereit und ihre Maße werden vom Druckertreiber für die korrekte Skalierung der Zeichnung verwendet.
A template provides a background for the drawing tasks and its dimensions are used by the printer drivers to scale the drawing correctly.


Die Vorlagen sind SVG-Dateien, daher muss ein Makro einige Zeilen SVG-Kode (eine Untermenge von XML-Kode) zusammenstellen.
The templates are svg-files and so a macro has to compose some lines of svg code (which is a subset of xml code).


'''Hinweis:''' nachdem FreeCAD von '''freecadweb.org''' nach '''freecad.org''' umgezogen ist, wurde diese Seite entsprechend aktualisiert und der resultierende SVG-Code ist nicht mehr kompatibel mit FreeCAD-Versionen, die älter sind als v0.21. Für diese versionen muss {{Incode|freecad.org}} in der Deklarationszeile des resultierenden SVG-Codes von Hand auf {{Incode|freecadweb.org}} zurückgeändert werden, da die editierbaren Texte sonst nicht erkannt werden.
== Structure of a simple blank page ==


<span id="Structure_of_a_simple_blank_page"></span>
The SVG format is a subset of the XML format.
== Struktur einer einfachen leeren Seite ==
That is why an SVG file, like any XML file, consists of two parts:
* Head, a format declaration
* Body, containing the information what to show and where to place it
: (I don't know why there should be a headline, the svg-file is still a valid template file without it...)


Das SVG-Format ist eine Untermenge des XML-Formats.
=== Head ===
Daher besteht eine SVG-Datei, wie auch jede XML-Datei, aus zwei Teilen:
* Einem Kopf mit eine Formatdeklaration
* Einem Körper, der die Informationen darüber enthält, was anzuzeigen ist und wo es platziert wird
:(Ich weiß nicht, warum die Kopfzeile enthalten sein soll; die SVG-Datei funktioniert auch ohne sie als Vorlagedatei)


<span id="Head"></span>
The head is just one line to declare which version of the XML language an interpreter should use to handle the instructions in the body.
=== Kopf ===

Der Kopf besteht aus nur einer Zeile zur Angabe der Version der XML-Sprache, die verwendet werden soll, um die im Körper befindlichen Anweisungen zu verarbeiten.


{{Code|lang=xml|code=
{{Code|lang=xml|code=
Line 38: Line 43:
}}
}}


<span id="Body"></span>
=== Body ===
=== Körper ===


Der Körper startet mit einem öffnenden Tag, der Informationen über Namensräume, über die Größe der Vorlage und darüber, wo sie platziert wird enthält. Und er hört mit einem schließenden Tag auf.
The Body starts with an opening tag which contains information about name spaces and about the size of the template and where to place it. And it finishes with a closing tag.


{{Code|lang=xml|code=
{{Code|lang=xml|code=
<svg
<svg
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns:freecad="http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace"
xmlns:freecad="http://www.freecad.org/wiki/index.php?title=Svg_Namespace"
width="420mm"
width="420mm"
height="297mm"
height="297mm"
Line 52: Line 58:
}}
}}


: '''xmlns='''"http://www.w3.org/2000/svg": External link to the xml name space to look up standard xml commands
: '''xmlns='''"http://www.w3.org/2000/svg": Externer Verweis auf den XML-Namensraum, um Standard-XML-Befehle nachzuschlagen.
: '''version='''"1.1": Used xml version is 1.1
: '''version='''"1.1": Die verwendete XML-Version ist 1.1
: '''xmlns:freecad='''"...=Svg_Namespace": Externe Verknüpfung zu FreeCADs Wikiseite [[Svg_Namespace/de|Svg-Namensraum]]". Die Verknüpfung wird nicht zum Nachladen von Informationen oder Werten zur Laufzeit genutzt, sondern als Schlüssel zum Aktivieren der speziellen Attribute, wie z.B. die editierbare Texte.
: '''xmlns:freecad='''"http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace": External link to FreeCAD's name space extension
: "freecad:" wird den besagten speziellen Attributen vorangestellt.
:: to look up special commands that are only used inside a FreeCAD environment e.g. for editable texts
: '''width='''"420mm": Width of the drawing area
: '''width='''"420mm": Breite der Zeichenfläche.
: '''height='''"297mm": Height of the drawing area
: '''height='''"297mm": Höhe der Zeichenfläche.
: '''viewBox='''"0 0 420 297": Position of the upper left corner (0;0) and the lower right corner (420;297) in the svg construction space (in svg units).
: '''viewBox='''"0 0 420 297": Position der oberen linken Ecke (0;0) und der unteren rechten Ecke (420;297) im SVG-Konstruktionsraum (in SVG-Einheiten).
: Width, height, and viewBox in this combination set 1 svg-unit to 1 mm for the whole document. A dimensional unit can be omitted from now on.
: Width, height, and viewBox, in dieser Kombination, setzen 1 SVG-Einheit gleich 1 mm, für das gesamte Dokument. Von jetzt an kann die Maßeinheit weggelassen werden.
: In this case 420 and 297 give an A3 page. Customise these values to generate other page sizes
: In diesem Falle ergeben 420 und 297 eine A3-Seite. Diese Werte werden angepasst, um andere Seitenformate zu erzeugen.


Für eine leere DIN-A3-Seite im Querformat war das alles.
For a blank page size DIN A3 in landscape orientation that's all.


{{Code|lang=xml|code=
{{Code|lang=xml|code=
Line 68: Line 74:
<svg
<svg
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns:freecad="http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace"
xmlns:freecad="http://www.freecad.org/wiki/index.php?title=Svg_Namespace"
width="420mm"
width="420mm"
height="297mm"
height="297mm"
Line 75: Line 81:
}}
}}


<span id="Python_code..."></span>
== Python code... ==
== Python-Kode... ==


Die Skripterstellung beginnt mit einem Gerüst, das eine separate Hauptfunktion "main" enthält
Before any code is generated a folder is needed to store the new template file.


{{Code| |code=
The user should have selected a template folder. Its path is then stored in the TechDraw preferences. </br>
#! python
It is not necessary to know where the preferences are store, because FreeCAD has commands to address needed parameters directly.
# -*- coding: utf-8 -*-
# (c) 2024 Your name LGPL


def main():
"""Here is where the magic happens"""
return

if __name__ == '__main__':
# This will be true only if the file is "executed"
# but not if imported as module
main()
}}

<span id="..._to_create_a_blank_page"></span>
=== ... zum Erstellen einer leeren Seite ===

Der Ablauf zur Erstellung einer Vorlage besteht aus den Schritten:
* Den Volagenordner lokalisieren.
* Eine Datei zum Schreiben zu öffnen und so als ersten Schritt eine leere SVG-Datei anzulegen, eine Kopfzeile zu schreiben und die Datei zu schließen.
* Danach wiederholt die Datei zum Anhängen weiterer Segmente jeweils öffnen und nach erfolgtem Anhängen wieder schließen.

Das Makro besteht aus mehreren Funktionen, die von der Hauptfunktion "main()" aufgerufen werden.<br>
Zusätzliche Funktionen können vor der EndSvg-Funktion eingefügt werden und die dazugehörigen Aufrufe werden vor dem EndSvg()-Aufruf eingesetzt.<br>
Man muss auf die Anzahl der Leerzeichen achten, die mit den Schreiboperationen erzeugt werden, und damit auf den korrekten Zeileneinzug.

==== pathToTemplate() ====

Bevor überhaupt Kode erzeugt wird, benötigt man einen Ordner zum Speichern der neuen Vorlagen und es muss ein Dateiname vergeben werde.

Der Benutzer sollte bereits einen Vorlagenordner ausgewählt haben. Sein Pfad ist dann in den TechDraw-Voreinstellungen gespeichert. </br> Es ist nicht nötig zu wissen, wo die Voreinstellungen gespeichert werden, da FreeCAD Befehle enthält, mit denen man die benötigten Parameter direkt ansprechen kann.


{{Code| |code=
{{Code| |code=
def pathToTemplate(template_name):
parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
"""Link a given template name to the path of the template folder"""
template_path = parameter_path.GetString("TemplateDir")
#- Get the path to the template folder that is set in the FreeCAD parameters
template_name = "MyTemplate.svg"
parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
template_file = os.path.join(template_path, template_name)
template_path = parameter_path.GetString("TemplateDir")
#- Link template_path and template_name for any OS
path_to_file = os.path.join(template_path, template_name) # to join path segments OS neutral
return path_to_file
}}
}}


'''parameter_path''' receives the path to the "folder" within the configuration file where "TemplateDir" parameter can be found. </br>
'''parameter_path''' erhält den Pfad zum "Verzeichnis" innerhalb der Konfigurationsdatei, wo die Variable "TemplateDir" gefunden werden kann.</br>
'''template_path''' receives the content of "TemplateDir" which is the path to the template directory. </br>
'''template_path''' erhält den Inhalt von "TemplateDir", also den Pfad zum Vorlagenverzeichnis.</br>
'''template_name''' recieves the name of the template to be created.
'''template_name''' erhält den Namen der neu zu erstellenden Vorlage.


Nun muss der '''template_name''' noch so mit dem '''template_path''' verknüpft werden, dass das Ergebnis sowohl zu unix-basierten Betriebssystemen als auch zu Windows kompatibel ist.</br>
Now the template name needs to be linked to the template path in a way that is compatible to unix based OSs and Windows.</br>
This is done with the "os.path.join" command and stored into the '''template_file'''.
Dies wird mit dem Befehl "os.path.join" erreicht und in der Variablen '''template_file''' gespeichert. Um diesen Befehl zu aktivieren, wird eine Importanweisung "import os" verlangt.


=== ... to create a blank page ===
==== createSvgFile() ====

Dies erstellt eine neue Vorlage und speichert die XML-Kopfzeile.

{{Code| |code=
def createSvgFile(file_path):
# Create a file and insert a header line (with t as the space saving variant of template)
t = open(file_path, "w") # w = write, overwrites existing files
t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
t.close
}}

==== startSvg() ====

Dies wird an die Vorlagendatei angefügt und erstellt den öffnenden SVG-Tag inklusive seiner Attribute.<br>Jede Schreibanweisung enthält eine einzelne SVG-Codezeile, die auf "\n" endet, dem Kennzeichen für CR/LF.

{{Code| |code=
def startSvg(file_path, sheet_width, sheet_height):
# Create svg-tag including namespace and format definitions
t = open(file_path, "a", encoding="utf-8")
# a = append, new lines are added at the end of an existing file
# encoding="utf-8", helps with special characters if the Python interpreter is in ASCII mode
t.write("\n" + "\n")
t.write("<svg\n")
#- Namespace declarations
t.write(" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
t.write(" xmlns:freecad=\"http://www.freecad.org/wiki/index.php?title=Svg_Namespace\"\n")
#- Format definition
t.write(" width =\"" + sheet_width + "mm\"\n")
t.write(" height=\"" + sheet_height + "mm\"\n")
t.write(" viewBox=\"0 0 " + sheet_width + " " + sheet_height + "\">\n")
# identical values for width and height and Viewbox' width and height
# will synchronise mm and svg-units
t.close
}}

<span id="endSvg()"></span>
====Funktion endSvg() ====

Dies wird wieder an die Vorlagendatei angefügt und erstellt den schließenden SVG-Tag; Damit wird der SVG-Code letztlich abgeschlossen.

{{Code| |code=
def endSvg(file_path):
# Create closing svg-tag
t = open(file_path, "a", encoding="utf-8")
t.write("</svg>")
t.close
}}

<span id="main()"></span>
====Funktion main()====

Die Funktion main() ruft alle weiteren Funktionen auf und übergibt einige Parameter.

{{Code| |code=
def main():
"""This one creates an empty A3 template"""
#- Set the name of the template file and get its location
template_file = pathToTemplate("MyTemplate.svg") # Change the template name here
#- Here starts the compiling of the svg file
createSvgFile(template_file) # overwrites existing File
#- Set sheet format (DIN A3)
format_width = "420"
format_height = "297"
startSvg(template_file, format_width, format_height) # adds svg start tag
endSvg(template_file) # adds svg end tag
# At this point a new SVG-file is generated and saved
return
}}

In diesem Beispiel sind die Werte für {{Incode|format_width}} und {{Incode|format_height}} festgeschriebene (hard coded) Abmessungen, Die beiden eigentlich nicht notwendigen Zeilen markieren den Punkt an dem andere Funktionen zum Ermitteln der Formatdaten ihren Inhalt ablegen können.


<div class="mw-collapsible mw-collapsed toccolours">
<div class="mw-collapsible mw-collapsed toccolours">
==== Das komplette Makro für eine leere Seite ====
This macro shows the principle how an svg-file can be put together. (Format is A3)

Dieses Makro enthält die obigen Codeabschnitte und ist startbereit.
<div class="mw-collapsible-content">
<div class="mw-collapsible-content">


Line 105: Line 219:
#! python
#! python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# (c) 2021 Your name LGPL
# (c) 2024 Your name LGPL
#
#
#- Get the path to the template folder that is set in the FreeCAD parameters
parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
template_path = parameter_path.GetString("TemplateDir")
template_name = "MyTemplate.svg"
#- Link template_path and template_name for any OS
template_file = os.path.join(template_path, template_name)


import os # to enable the use of os.path.join()
# - SVG creation -



#- Create a file and insert a header line
# - SVG creation -
# (with t as the space saving variant of template)
def CreateSvgFile(filePath):
def createSvgFile(file_path):
# Create a file and insert a header line (with t as the space saving variant of template)
t=open(filePath,"w") # w = write, overwrites existing files
t = open(file_path, "w") # w = write, overwrites existing files
t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
t.close
t.close


def startSvg(file_path, sheet_width, sheet_height):
#- Create opening svg-tag
# Create svg-tag including namespace and format definitions
# Namespace section
t = open(file_path, "a", encoding="utf-8")
def StartSvg(filePath):
t=open(filePath,"a") # a = append, new lines are added at the end of an existing file
# a = append, new lines are added at the end of an existing file
# encoding="utf-8", helps with special characters if the Python interpreter is in ASCII mode
t.write("\n"+"\n")
t.write("\n" + "\n")
t.write("<svg\n")
t.write("<svg\n")
#- Namespace declarations
t.write(" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
t.write(" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
t.write(" xmlns:freecad=\"http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace\"\n")
t.write(" xmlns:freecad=\"http://www.freecad.org/wiki/index.php?title=Svg_Namespace\"\n")
#- Format definition
t.close
t.write(" width =\"" + sheet_width + "mm\"\n")
# Sheet size section
t.write(" height=\"" + sheet_height + "mm\"\n")
def CreateSheet(filePath,shWidth,shHeight):
t.write(" viewBox=\"0 0 " + sheet_width + " " + sheet_height + "\">\n")
t=open(filePath,"a")
# identical values for width and height and Viewbox' width and height
t.write(" width =\""+shWidth+"mm\"\n")
# will synchronise mm and svg-units
t.write(" height=\""+shHeight+"mm\"\n")
t.write(" viewBox=\"0 0 "+shWidth+" "+shHeight+"\">\n")
t.close
t.close
# identical values for width and height and Viewbox' width and height will synchronise mm and svg-units


def endSvg(file_path):
#- Create closing svg-tag
# Create closing svg-tag
def EndSvg(filePath):
t=open(filePath,"a")
t = open(file_path, "a", encoding="utf-8")
t.write("</svg>")
t.write("</svg>")
t.close
t.close


def pathToTemplate(template_name):
# --- Main section ---
"""Link a given template name to the path of the template folder"""
#- Get the path to the template folder that is set in the FreeCAD parameters
parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
template_path = parameter_path.GetString("TemplateDir")
#- Link template_path and template_name for any OS
path_to_file = os.path.join(template_path, template_name) # to join path segments OS neutral
return path_to_file


def main():
CreateSvgFile(template_file) # overwrites existing File
"""This one creates an empty A3 template"""
#- Set the name of the template file and get its location
template_file = pathToTemplate("MyTemplate.svg") # Change the template name here
#- Here starts the compiling of the svg file
createSvgFile(template_file) # overwrites existing File
#- Set sheet format (DIN A3)
format_width = "420"
format_height = "297"
startSvg(template_file, format_width, format_height) # adds svg start tag
endSvg(template_file) # adds svg end tag
# At this point a new SVG-file is generated and saved
return


if __name__ == '__main__':
# Set sheet format (DIN A3)
# This will be true only if the file is "executed"
formatWidth = "420"
# but not if imported as module
formatHeight = "297"
main()

StartSvg(template_file) # adds Start tag and namespaces
CreateSheet(template_file, formatWidth, formatHeight)
EndSvg(template_file)
# At this point a new SVG-file is generated and saved
}}
}}


Line 165: Line 287:
</div> <!-- End of collapsible element for ... section. Do not remove! -->
</div> <!-- End of collapsible element for ... section. Do not remove! -->


: The main principle is:
:* to open a file for writing and so start an svg-file from scratch, write the header line and close the file as a first step.
:* and then to repeatedly open the file to append further segments and then close it again after appending.
:
:The macro is made of several functions which are called from the main section.
:Additional functions could be inserted before the EndSvg function and the needed calls are put before the EndSvg() call.
:We need to have an eye on the number of spaces used with the write operations for a correct indentation.


<span id="..._to_create_a_page_with_some_ink"></span>
=== ... to create a page with some ink ===
=== ... zum Erstellen einer Seite mit ein paar Strichen ===


Um aus einer leeren Seite eine Zeichnung zu machen, braucht man:
To make a drawing out of a blank page we need:
:* Frames i.e. rectangles created with the '''rect''' instruction
* Rahmen, d.h. Rechtecke, die mit der Anweisung '''rect''' erstellt werden.
:* a title block and more made of lines created with the '''path''' instruction
* Ein Schriftfeld und noch etwas mehr, das aus Linien besteht, die mit der Anweisung '''path''' erstellt werden.
* Einfache Texte für Indizes und die Benennungen der Schriftfeldzellen.
:* simple texts for indexes and labeling title block cells
* Editierbare Texte für z.B. Sachnummer und Benennung.
:* editable texts like part number or part name


Diese graphischen Elemente werden normalerweise mehrfach benutzt, daher werden für die erzeugenden Kodes vier Funktionen erstellt:
Normally these graphical elements are used repeatedly and so the generating code is put into functions.
* svgRect() für rechteckige Rahmenelemente.
* svgPath() für gerade Linienelemente.
* svgText() für unveränderliche Texte.
* ediText() für editierbares Texte.


Alle Funktionen werden innerhalb des obigen Makros vor der Funktion main() eingefügt und die dazugehörigen Funktionsaufrufe zwischen {{Incode|startSvg(...)}} und {{Incode|endSvg(...)}} in der Funktion main().
=== svgrect function ===


<span id="svgRect()"></span>
To draw a rectangle we just need to call the '''svgrect''' function and hand over the values for width, height, and position of the upper left corner. That is better readable than the content of the svgline which had to be used instead.
===Funktion svgRect()===

Um ein Rechteck zu zeichnen, muss die Funktion '''svgRect''' aufgerufen und die Werte für Breite, Höhe und die Position der linken oberen Ecke übergeben werden. Das liest sich an dieser Stelle besser als wenn svg_line die komplette SVG-Codezeile enthalten würde.


{{Code| |code=
{{Code| |code=
def svgRect(width, height, x, y):
#- Function to generate an svg-instruction to draw a rectangle with the given values
# Generates an svg-instruction to draw a rectangle with the given values
def svgrect(width,height,x,y):
svg_line = (
svgLine=("<rect width=\""+width+"\" height=\""+height+"\" x=\""+x+"\" y=\""+y+"\" />")
"<rect width=\"" + width + "\" height=\"" + height + "\" x=\"" + x
return svgLine
+ "\" y=\"" + y + "\" />"
)
return svg_line
}}
}}


Die SVG-Anweisung ist geteilt, um innerhalb der empfohlenen Python-Zeilenlänge zu bleiben, sie ergibt trotzdem eine einzelne SVG-Zeile.
=== svgpath function ===

<span id="svgPath()"></span>
===Funktion svgPath()===


Um eine Linie zu zeichnen, muss die Funktion '''svgPath''' aufgerufen und die Koordinaten für Start- und Endpunkt einer Linie übergeben werden.
To draw a line we just need to call the '''svgpath''' function and hand over the coordinates of the start point and the endpoint of a line.


Anstatt der Endpunktkoordinaten kann ein Kennwert übergeben werden, um eine horizontale (h) oder vertikale (v) Linie zu zeichnen, gefolgt von der Länge der Linie, oder ein Kennwert, um eine Horizontale (H) oder vertikale (V) Linie zu zeichnen, gefolgt von der X- bzw. Y-Ordinate des Endpunktes.
Instead of the end point coordinates we can hand over a tag to draw a horizontal (h) or vertical (v) line followed by the length of the line or the tag to draw a horizontal (H) or vertical (V) line followed by the x or y ordinate of the end point.


Each path starts at the origin and the first action is a movement with "raised pen" (not drawing) to the start point. In this case the relative movement and the absolute movement are the same and so it is irrelevant whether the m-tag is upper or lower case.
Jeder Pfad beginnt am Ursprung und die erste Aktion ist eine Bewegung mit "angehobenem Stift" (nicht zeichnend) zum Startpunkt. In diesem Fall sind die relative und die absolute Bewegung identisch und daher ist es egal, ob der M-Tag ein großes oder ein kleines M enthält.


{{Code| |code=
{{Code| |code=
def svgPath(x1, y1, x2, y2):
#- Function to generate an svg-instruction to draw a path element (line) with the given values
# Generates an svg-instruction to draw a path element (line) with the given values
def svgpath(x1,y1,x2,y2):
if x2=="v" or x2=="V" or x2=="h" or x2=="H":
if x2 == "v" or x2 == "V" or x2 == "h" or x2 == "H":
svgLine=("<path d=\"m "+x1+","+y1+" "+x2+" "+y2+"\" />")
svg_line = ("<path d=\"m " + x1 + "," + y1 + " " + x2 + " " + y2 + "\" />")
else:
else:
svgLine=("<path d=\"m "+x1+","+y1+" l "+x2+","+y2+"\" />")
svg_line = ("<path d=\"m " + x1 + "," + y1 + " l " + x2 + "," + y2 + "\" />")
return svgLine
return svg_line
}}
}}


<span id="svgText()"></span>
=== svgtext function ===
===Funktion svgText()===


Um etwas Text zu zeichnen, muss die Funktion '''svgText''' aufgerufen und die Koordinaten für den Ankerpunkt des Textes, die Textzeichenkette selbst sowie wahlweise ein Winkel für gedrehten Text übergeben werden. Zum Drehen von Texten muss eine Transformationsanweisung in jeden einzelnen Text-Tag eingefügt werden; die Drehpunkte müssen dieselben Koordinaten erhalten, wie die zugehörigen Text-Ankerpunkte.
To draw a piece of text we just need to call the '''svgtext''' function and hand over the coordinates of the text's anchor point and the text string itself.

(Die Textausrichtung wird durch den umschließenden Gruppen-Tag gesteuert oder ist standardmäßig linksbündig)


(The text alignment is controlled by the surrounding group tag or left-aligned as default).
{{Code| |code=
{{Code| |code=
def svgText(x, y, str_value, str_angle="0"):
#- Function to generate an svg-instruction to place a text element with the given values
"""
def svgtext(posX,posY,strValue):
Generates an svg-instruction to place a text element with the given values.
svgLine=("<text x=\""+posX+"\" y=\""+posY+"\">"+strValue+"</text>")
Optional str_angle enables vertical and arbitrarily rotated texts
return svgLine
"""
if str_angle == "0":
svg_line = ("<text x=\"" + x + "\" y=\"" + y + "\">" + str_value + "</text>")
else:
svg_line = (
"<text x=\"" + x + "\" y=\"" + y + "\" transform=\"rotate(" + str_angle
+ "," + x + "," + y + ")\">" + str_value + "</text>"
)
return svg_line
}}
}}


<span id="ediText()"></span>
=== FCeditext function ===
====Funktion ediText()====


Um einen editierbaren Text zu zeichnen, muss die Funktion '''ediText''' aufgerufen und die Koordinaten für den Ankerpunkt des Textes, die Textzeichenkette selbst sowie wahlweise ein Winkel für gedrehten Text übergeben werden. Zum Drehen von Texten muss eine Transformationsanweisung in jeden einzelnen Text-Tag eingefügt werden; die Drehpunkte müssen dieselben Koordinaten erhalten, wie die zugehörigen Text-Ankerpunkte.
To draw a piece of editable text we just need to call the '''FCeditext''' function and hand over a name, the coordinates of the text's anchor point, and the text string itself.


FreeCAD creates a dictionary object with every inserted template, and each entry has a name (key) and a value.
FreeCAD erzeugt ein Dictionary-Objekt für jede hinzugefügte Vorlage, und jeder Eintrag hat einen Namen (key) und einen Wert (value).


(Die Textausrichtung wird durch den umschließenden Gruppen-Tag gesteuert oder ist standardmäßig linksbündig)
(The text alignment is controlled by the surrounding group tag or left-aligned as default).


{{Code| |code=
{{Code| |code=
def ediText(entry_name, x, y, str_value, str_angle="0"):
#- Function to generate an svg-instruction to place an editable text element with the given values
"""
def FCeditext(entryName,posX,posY,strValue):
Generates an svg-instruction to place an editable text element with the given values.
svgLine=("<text freecad:editable=\""+entryName+"\" x=\""+posX+"\" y=\""+posY \
Optional str_angle enables vertical and arbitrarily rotated editable texts
+"\"> <tspan>"+strValue+"</tspan> </text>")
return svgLine
"""
if str_angle == "0":
svg_line = (
"<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
+ "\"> <tspan>" + str_value + "</tspan> </text>"
)
else:
svg_line = (
"<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
+ "\" transform=\"rotate(" + str_angle + "," + x + "," + y + ")\"> <tspan>"
+ str_value + "</tspan> </text>"
)
return svg_line
}}
}}


<span id="createXxxx_functions"></span>
=== CreateXxxx functions ===
===Funktionstyp createXxxx ===


Diese Funktionen beginnen mit Kode zum öffnen einer Datei im Anfüge-Modus (append mode) sowie zum schreiben des öffnenden Gruppen-Tags.
These functions start with code to open a file in append mode and to write the group opening tag.


Dann folgt ein Abschnitt zum setzen und berechnen von Werten und mit Schreibanweisungen, die die obigen Funktionen zum erstellen von SVG-Kode aufrufen.
Then follows a section to set and calculate values and with write instructions that call the above functions to generate svg-code.


Und am Ende steht der schließende Gruppen-Tag, gefolgt von einer Anweisung zum Schließen der Datei.
And finally the group closing tag followed by an instruction to close the file.


{{Code| |code=
{{Code| |code=
def createFrame(file_path, sheet_width, sheet_height):
#- Frame creation
# Creates rectangles for sheet frame and drawing area
def CreateFrame(filePath,shWidth,shHeight):
t=open(filePath,"a")
t = open(file_path, "a", encoding="utf-8")
t.write(" <g id=\"drawing-frame\"\n")
t.write(" <g id=\"drawing-frame\"\n")
t.write(" style=\"fill:none;stroke:#000000;stroke-width:0.5;\
t.write(" style=\"fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round\">\n")
#- upper left corner of inner Frame, drawing area
stroke-linecap:round\">\n")
frame_x = str(20)
# inner Frame, drawing area
#- upper left corner
frame_y = str(10)
drawingX=str(20)
drawingY=str(10)
#- frame dimensions
#- frame dimensions
drawingWidth=str(int(shWidth)-20-10)
frame_width = str(int(sheet_width) - 20 - 10)
drawingHeight=str(int(shHeight)-10-10)
frame_height = str(int(sheet_height) - 10 - 10)
#- frame rectangle
#- frame rectangle
t.write(" "+svgrect(drawingWidth,drawingHeight,drawingX,drawingY)+"\n")
t.write(" " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
# outer frame
#- upper left corner outer frame, sheet frame
#- upper left corner
frame_x = str(15)
drawingX=str(15)
frame_y = str(5)
drawingY=str(5)
#- frame dimensions
#- frame dimensions
drawingWidth=str(int(shWidth)-20)
frame_width = str(int(sheet_width)-20)
drawingHeight=str(int(shHeight)-10)
frame_height = str(int(sheet_height)-10)
#- frame rectangle
#- frame rectangle
t.write(" "+svgrect(drawingWidth,drawingHeight,drawingX,drawingY)+"\n")
t.write(" " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
t.write(" </g>\n\n")
t.write(" </g>\n\n")
t.close
t.close
}}
}}


<span id="Resulting_macro"></span>
=== Resulting macro ===
=== Das resultierende Makro ===


<div class="mw-collapsible mw-collapsed toccolours">
<div class="mw-collapsible mw-collapsed toccolours">
Dieses Makro fügt ein paar grundlegende graphische Elemente hinzu, die für richtige Vorlagen gebraucht werden, wie z.B. Linienelemente, Texte und editierbares Texte.
This macro adds some basic graphical elements needed for proper templates i.e. line elements, texts, and editable texts.
<div class="mw-collapsible-content">
<div class="mw-collapsible-content">


Line 287: Line 438:
#! python
#! python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# (c) 2021 Your name LGPL
# (c) 2024 Your name LGPL
#
#
#- Get the path to the template folder that is set in the FreeCAD parameters
parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
template_path = parameter_path.GetString("TemplateDir")
template_name = "MyTemplate.svg"
#- Link template_path and template_name for any OS
template_file = os.path.join(template_path, template_name)


import os # to enable the use of os.path.join()
# - SVG creation -



#- Create a file and insert a header line
# - SVG creation -
# (with t as the space saving variant of template)
def CreateSvgFile(filePath):
def createSvgFile(file_path):
# Create a file and insert a header line (with t as the space saving variant of template)
t=open(filePath,"w") # w = write, overwrites existing files
t = open(file_path, "w") # w = write, overwrites existing files
t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
t.close
t.close


def startSvg(file_path, sheet_width, sheet_height):
#- Create opening svg-tag
# Create svg-tag including namespace and format definitions
# Namespace section
t = open(file_path, "a", encoding="utf-8")
def StartSvg(filePath):
t=open(filePath,"a") # a = append, new lines are added at the end of an existing file
# a = append, new lines are added at the end of an existing file
# encoding="utf-8", helps with special characters if the Python interpreter is in ASCII mode
t.write("\n"+"\n")
t.write("\n" + "\n")
t.write("<svg\n")
t.write("<svg\n")
#- Namespace declarations
t.write(" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
t.write(" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
t.write(" xmlns:freecad=\"http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace\"\n")
t.write(" xmlns:freecad=\"http://www.freecad.org/wiki/index.php?title=Svg_Namespace\"\n")
#- Format definition
t.write(" width =\"" + sheet_width + "mm\"\n")
t.write(" height=\"" + sheet_height + "mm\"\n")
t.write(" viewBox=\"0 0 " + sheet_width + " " + sheet_height + "\">\n")
# identical values for width and height and Viewbox' width and height
# will synchronise mm and svg-units
t.close
t.close
# Sheet size section
def CreateSheet(filePath,shWidth,shHeight):
t=open(filePath,"a")
t.write(" width =\""+shWidth+"mm\"\n")
t.write(" height=\""+shHeight+"mm\"\n")
t.write(" viewBox=\"0 0 "+shWidth+" "+shHeight+"\">\n")
t.close
# identical values for width and height and Viewbox' width and height will synchronise mm and svg-units


def endSvg(file_path):
#- Create closing svg-tag
# Create closing svg-tag
def EndSvg(filePath):
t=open(filePath,"a")
t = open(file_path, "a", encoding="utf-8")
t.write("</svg>")
t.write("</svg>")
t.close
t.close


def svgRect(width, height, x, y):
#- Function to generate an svg-instruction to draw a rectangle with the given values
# Ggenerates an svg-instruction to draw a rectangle with the given values
def svgrect(width,height,x,y):
svg_line = (
svgLine=("<rect width=\""+width+"\" height=\""+height+"\" x=\""+x+"\" y=\""+y+"\" />")
"<rect width=\"" + width + "\" height=\"" + height + "\" x=\"" + x
return svgLine
+ "\" y=\"" + y + "\" />"
)
return svg_line


def svgPath(x1, y1, x2, y2):
#- Function to generate an svg-instruction to draw a path element (line) with the given values
# Generates an svg-instruction to draw a path element (line) with the given values
def svgpath(x1,y1,x2,y2):
if x2=="v" or x2=="V" or x2=="h" or x2=="H":
if x2 == "v" or x2 == "V" or x2 == "h" or x2 == "H":
svgLine=("<path d=\"m "+x1+","+y1+" "+x2+" "+y2+"\" />")
svg_line = ("<path d=\"m " + x1 + "," + y1 + " " + x2 + " " + y2 + "\" />")
else:
else:
svgLine=("<path d=\"m "+x1+","+y1+" l "+x2+","+y2+"\" />")
svg_line = ("<path d=\"m " + x1 + "," + y1 + " l " + x2 + "," + y2 + "\" />")
return svgLine
return svg_line


def svgText(x, y, str_value, str_angle="0"):
#- Function to generate an svg-instruction to place a text element with the given values
"""
def svgtext(posX,posY,strValue):
Generates an svg-instruction to place a text element with the given values.
svgLine=("<text x=\""+posX+"\" y=\""+posY+"\">"+strValue+"</text>")
Optional str_angle enables vertical and arbitrarily rotated texts
return svgLine
"""
if str_angle == "0":
svg_line = ("<text x=\"" + x + "\" y=\"" + y + "\">" + str_value + "</text>")
else:
svg_line = (
"<text x=\"" + x + "\" y=\"" + y + "\" transform=\"rotate(" + str_angle
+ "," + x + "," + y + ")\">" + str_value + "</text>"
)
return svg_line


def ediText(entry_name, x, y, str_value, str_angle="0"):
#- Function to generate an svg-instruction to place an editable text element with the given values
"""
def FCeditext(entryName,posX,posY,strValue):
Generates an svg-instruction to place an editable text element with the given values.
svgLine=("<text freecad:editable=\""+entryName+"\" x=\""+posX+"\" y=\""+posY \
Optional str_angle enables vertical and arbitrarily rotated editable texts
+"\"> <tspan>"+strValue+"</tspan> </text>")
return svgLine
"""
if str_angle == "0":
svg_line = (
"<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
+ "\"> <tspan>" + str_value + "</tspan> </text>"
)
else:
svg_line = (
"<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
+ "\" transform=\"rotate(" + str_angle + "," + x + "," + y + ")\"> <tspan>"
+ str_value + "</tspan> </text>"
)
return svg_line


def createFrame(file_path, sheet_width, sheet_height):
#- Frame creation
# Creates rectangles for sheet frame and drawing area
def CreateFrame(filePath,shWidth,shHeight):
t=open(filePath,"a")
t = open(file_path, "a", encoding="utf-8")
t.write(" <g id=\"drawing-frame\"\n")
t.write(" <g id=\"drawing-frame\"\n")
t.write(" style=\"fill:none;stroke:#000000;stroke-width:0.5;\
t.write(" style=\"fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round\">\n")
#- calculate upper left corner of inner Frame, drawing area
stroke-linecap:round\">\n")
frame_x = str(20)
# inner Frame, drawing area
#- upper left corner
frame_y = str(10)
drawingX=str(20)
drawingY=str(10)
#- frame dimensions
#- frame dimensions
drawingWidth=str(int(shWidth)-20-10)
frame_width = str(int(sheet_width) - 20 - 10)
drawingHeight=str(int(shHeight)-10-10)
frame_height = str(int(sheet_height) - 10 - 10)
#- frame rectangle
#- frame rectangle
t.write(" "+svgrect(drawingWidth,drawingHeight,drawingX,drawingY)+"\n")
t.write(" " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
# outer frame
#- calculate upper left corner outer frame, sheet frame
#- upper left corner
frame_x = str(15)
drawingX=str(15)
frame_y = str(5)
drawingY=str(5)
#- frame dimensions
#- frame dimensions
drawingWidth=str(int(shWidth)-20)
frame_width = str(int(sheet_width)-20)
drawingHeight=str(int(shHeight)-10)
frame_height = str(int(sheet_height)-10)
#- frame rectangle
#- frame rectangle
t.write(" "+svgrect(drawingWidth,drawingHeight,drawingX,drawingY)+"\n")
t.write(" " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
t.write(" </g>\n\n")
t.write(" </g>\n\n")
t.close
t.close


def createTitleBlock(file_path, sheet_width, sheet_height):
#- Title block movable
"""Creates a title block and transfers it to the position according to the sheet dimensions"""
def CreateTitleBlock(filePath,shWidth,shHeight):
#- calculate title block origin (lower left corner), offset from page origin

tbX = str(int(sheet_width) - 10 - 180) # 180 according to DIN EN ISO 7200
#- lower left corner
tbX=str(int(shWidth)-10-180) # 180 according to DIN EN ISO 7200
tbY = str(int(sheet_height) - 10)
t = open(file_path, "a", encoding="utf-8")
tbY=str(int(shHeight)-10)
#- group to transform all elements at once
#- group to transfer all included title block elements at once
t=open(filePath,"a")
t.write(" <g id=\"titleblock\"\n")
t.write(" <g id=\"titleblock\"\n")
t.write(" transform=\"translate("+tbX+","+tbY+")\">\n")
t.write(" transform=\"translate(" + tbX + "," + tbY + ")\">\n")
#- title block
#- sub-group of title block line framework
t.write(" <g id=\"titleblock-frame\"\n")
t.write(" <g id=\"titleblock-frame\"\n")
t.write(" style=\"fill:none;stroke:#000000;stroke-width:0.35;\
t.write(" style=\"fill:none;stroke:#000000;stroke-width:0.35;\
stroke-linecap:miter;stroke-miterlimit:4\">\n")
stroke-linecap:miter;stroke-miterlimit:4\">\n")
t.write(" "+svgpath(" 0"," 0"," 0","-63")+"\n")
t.write(" " + svgPath(" 0"," 0"," 0","-63") + "\n")
t.write(" "+svgpath(" 0","-63","180"," 0")+"\n")
t.write(" " + svgPath(" 0","-63","180"," 0") + "\n")
t.write(" "+svgpath(" 0","-30","h","155")+"\n")
t.write(" " + svgPath(" 0","-30","h","155") + "\n")
t.write(" "+svgpath("155"," 0","v","-63")+"\n")
t.write(" " + svgPath("155"," 0","v","-63") + "\n")
t.write(" </g>\n")
t.write(" </g>\n")
#- small texts, left-aligned
#- sub-group of title block static texts (left-aligned by default)
t.write(" <g id=\"titleblock-text-non-editable\"\n")
t.write(" <g id=\"titleblock-text-non-editable\"\n")
t.write(" style=\"font-size:5.0;text-anchor:start;fill:#000000;\
t.write(" style=\"font-size:5.0;text-anchor:start;fill:#000000;\
font-family:osifont\">\n")
font-family:osifont\">\n")
t.write(" "+svgtext(" 4.5","-43.5 ","Some static text")+"\n")
t.write(" " + svgText(" 4.5","-43.5 ","Some static text") + "\n")
t.write(" "+svgtext(" 4.5","-13.5 ","More static text")+"\n")
t.write(" " + svgText(" 4.5","-13.5 ","More static text") + "\n")
t.write(" " + svgText("162.5","-3.5 ","Vertical static text","-90") + "\n")
t.write(" </g>\n")
t.write(" </g>\n")

t.write(" </g>\n\n")
t.write(" </g>\n\n")
t.close
t.close


def createEditableText(file_path, sheet_width, sheet_height):
#- Title block editable texts
"""Creates editable texts positioned according to the page origin"""
def CreateEditableText(filePath,shWidth,shHeight):
#- calculate offset to titleblock origin

edX = int(sheet_width) - 10 - 180 # 180 according to DIN EN ISO 7200
#- offsets for editable texts
edY = int(sheet_height) - 10
edX=int(shWidth)-10-180 # 180 according to DIN EN ISO 7200
t = open(file_path, "a", encoding="utf-8")
edY=int(shHeight)-10
#- group editable texts using the same attributes

t=open(filePath,"a")
t.write(" <g id=\"titleblock-editable-texts\"\n")
t.write(" <g id=\"titleblock-editable-texts\"\n")
t.write(" style=\"font-size:7.0;text-anchor:start;fill:#0000d0;\
t.write(" style=\"font-size:7.0;text-anchor:start;fill:#0000d0;\
font-family:osifont\">\n")
font-family:osifont\">\n")
t.write(
t.write(" "+FCeditext("EdiText-1",str(edX+60),str(edY-43.5),"Some editable text")+"\n")
t.write(" "+FCeditext("EdiText-2",str(edX+60),str(edY-13.5),"More editable text")+"\n")
" " + ediText("EdiText-1",str(edX + 60),str(edY - 43.5),"Some editable text") + "\n"
)
t.write(
" " + ediText("EdiText-2",str(edX + 60),str(edY - 13.5),"More editable text") + "\n"
)
t.write(
" " + ediText("EdiText-3",str(edX + 173),str(edY - 4.5),"90° editable text","-90")
+ "\n"
)
t.write(" </g>\n\n")
t.write(" </g>\n\n")
t.close
t.close


def pathToTemplate(template_name):
# --- Main section ---
"""Link a given template name to the path of the template folder"""
#- Get the path to the template folder that is set in the FreeCAD parameters
parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
template_path = parameter_path.GetString("TemplateDir")
#- Link template_path and template_name for any OS
path_to_file = os.path.join(template_path, template_name) # to join path segments OS neutral
return path_to_file


def main():
CreateSvgFile(template_file) # overwrites existing File
"""This one creates an A3 template with simple frame and title block"""
#- Set the name of the template file and get its location
template_file = pathToTemplate("MyTemplate.svg") # Change the template name here
#- Here starts the compiling of the svg file
createSvgFile(template_file) # overwrites existing File
#- Set sheet format (DIN A3)
format_width = "420"
format_height = "297"
startSvg(template_file, format_width, format_height) # adds svg start tag
createFrame(template_file, format_width, format_height)
createTitleBlock(template_file, format_width, format_height)
createEditableText(template_file, format_width, format_height)
endSvg(template_file) # adds svg end tag
# At this point a new SVG-file is generated and saved
return


if __name__ == '__main__':
# Set sheet format (A3)
# This will be true only if the file is "executed"
formatWidth = "420"
# but not if imported as module
formatHeight = "297"
main()

StartSvg(template_file) # adds Start tag and namespaces
CreateSheet(template_file, formatWidth, formatHeight)
CreateFrame(template_file, formatWidth, formatHeight)
CreateTitleBlock(template_file, formatWidth, formatHeight)
CreateEditableText(template_file, formatWidth, formatHeight)
EndSvg(template_file)
# At this point a new SVG-file is generated and saved
}}
}}


Und das ist der SVG-Kode aus diesem Makro:
And this is the svg-code coming out of this macro:


{{Code|lang=xml|code=
{{Code|lang=xml|code=
Line 451: Line 637:
<svg
<svg
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns:freecad="http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace"
xmlns:freecad="http://www.freecad.org/wiki/index.php?title=Svg_Namespace"
width ="420mm"
width ="420mm"
height="297mm"
height="297mm"
Line 474: Line 660:
<text x=" 4.5" y="-43.5 ">Some static text</text>
<text x=" 4.5" y="-43.5 ">Some static text</text>
<text x=" 4.5" y="-13.5 ">More static text</text>
<text x=" 4.5" y="-13.5 ">More static text</text>
<text x="162.5" y="-3.5 " transform="rotate(-90,162.5,-3.5 )">Vertical static text</text>
</g>
</g>
</g>
</g>
Line 481: Line 668:
<text freecad:editable="EdiText-1" x="290" y="243.5"> <tspan>Some editable text</tspan> </text>
<text freecad:editable="EdiText-1" x="290" y="243.5"> <tspan>Some editable text</tspan> </text>
<text freecad:editable="EdiText-2" x="290" y="273.5"> <tspan>More editable text</tspan> </text>
<text freecad:editable="EdiText-2" x="290" y="273.5"> <tspan>More editable text</tspan> </text>
<text freecad:editable="EdiText-3" x="403" y="282.5" transform="rotate(-90,403,282.5)"> <tspan>90° editable text</tspan> </text>
</g>
</g>


Line 489: Line 677:
</div> <!-- End of collapsible element for ... section. Do not remove! -->
</div> <!-- End of collapsible element for ... section. Do not remove! -->


Und so soll es aussehen, wenn sie (die Vorlage) eingefügt wurde (mit vergrößertem Schriftfeld):
And what it should look like when inserted (plus magnified title block):

[[Image:TechDraw TemplateGenerator.png|TechDraw TemplateGenerator.png]]


[[Image:TechDraw TemplateGenerator.png|600px]]


Die editierbaren Texte in blau einzufärben ist nur eine persönliche Vorliebe, um unveränderlichen und editierbaren Text einfacher unterscheiden zu können.
[[Category:Wiki]]

Latest revision as of 14:34, 3 February 2024

Other languages:
Tutorium
Thema
Eine TechDraw-Vorlagen unter Verwendung eines Python-Makros erstellen
Niveau
Grundkenntnisse von Python und SVG-Strukturen sind hilfreich
Zeit zum Abschluss
(noch nicht bekannt)
Autoren
FBXL5
FreeCAD-Version
0.21 und neuer
Beispieldateien
None
Siehe auch
Makro TemplateHelper

Einleitung

Diese Anleitung beschreibt, wie man eine einfache Vorlage zur Benutzung mit dem TechDraw-Arbeitsbereich aus ein paar Zeilen Python-Kode erstellt.

Jeder Texteditor kann zum Erstellen von Kode genutzt werden. Mein Favorit ist Atom, aber FreeCADs eingebauter Editor funktioniert auch bestens.

Die folgenden Kode-Beispiele können kopiert, in eine leere Textdatei eingefügt und dann unter einem selbstgewählten Namen als *.py oder *.FCMacro abgespeichert werden.

Eine Vorlage stellt einen Hintergrund für Zeichnungsaufgaben bereit und ihre Maße werden vom Druckertreiber für die korrekte Skalierung der Zeichnung verwendet.

Die Vorlagen sind SVG-Dateien, daher muss ein Makro einige Zeilen SVG-Kode (eine Untermenge von XML-Kode) zusammenstellen.

Hinweis: nachdem FreeCAD von freecadweb.org nach freecad.org umgezogen ist, wurde diese Seite entsprechend aktualisiert und der resultierende SVG-Code ist nicht mehr kompatibel mit FreeCAD-Versionen, die älter sind als v0.21. Für diese versionen muss freecad.org in der Deklarationszeile des resultierenden SVG-Codes von Hand auf freecadweb.org zurückgeändert werden, da die editierbaren Texte sonst nicht erkannt werden.

Struktur einer einfachen leeren Seite

Das SVG-Format ist eine Untermenge des XML-Formats. Daher besteht eine SVG-Datei, wie auch jede XML-Datei, aus zwei Teilen:

  • Einem Kopf mit eine Formatdeklaration
  • Einem Körper, der die Informationen darüber enthält, was anzuzeigen ist und wo es platziert wird
(Ich weiß nicht, warum die Kopfzeile enthalten sein soll; die SVG-Datei funktioniert auch ohne sie als Vorlagedatei)

Kopf

Der Kopf besteht aus nur einer Zeile zur Angabe der Version der XML-Sprache, die verwendet werden soll, um die im Körper befindlichen Anweisungen zu verarbeiten.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

Körper

Der Körper startet mit einem öffnenden Tag, der Informationen über Namensräume, über die Größe der Vorlage und darüber, wo sie platziert wird enthält. Und er hört mit einem schließenden Tag auf.

<svg
  xmlns="http://www.w3.org/2000/svg" version="1.1"
  xmlns:freecad="http://www.freecad.org/wiki/index.php?title=Svg_Namespace"
  width="420mm"
  height="297mm"
  viewBox="0 0 420 297">
</svg>
xmlns="http://www.w3.org/2000/svg": Externer Verweis auf den XML-Namensraum, um Standard-XML-Befehle nachzuschlagen.
version="1.1": Die verwendete XML-Version ist 1.1
xmlns:freecad="...=Svg_Namespace": Externe Verknüpfung zu FreeCADs Wikiseite Svg-Namensraum". Die Verknüpfung wird nicht zum Nachladen von Informationen oder Werten zur Laufzeit genutzt, sondern als Schlüssel zum Aktivieren der speziellen Attribute, wie z.B. die editierbare Texte.
"freecad:" wird den besagten speziellen Attributen vorangestellt.
width="420mm": Breite der Zeichenfläche.
height="297mm": Höhe der Zeichenfläche.
viewBox="0 0 420 297": Position der oberen linken Ecke (0;0) und der unteren rechten Ecke (420;297) im SVG-Konstruktionsraum (in SVG-Einheiten).
Width, height, and viewBox, in dieser Kombination, setzen 1 SVG-Einheit gleich 1 mm, für das gesamte Dokument. Von jetzt an kann die Maßeinheit weggelassen werden.
In diesem Falle ergeben 420 und 297 eine A3-Seite. Diese Werte werden angepasst, um andere Seitenformate zu erzeugen.

Für eine leere DIN-A3-Seite im Querformat war das alles.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
  xmlns="http://www.w3.org/2000/svg" version="1.1"
  xmlns:freecad="http://www.freecad.org/wiki/index.php?title=Svg_Namespace"
  width="420mm"
  height="297mm"
  viewBox="0 0 420 297">
</svg>

Python-Kode...

Die Skripterstellung beginnt mit einem Gerüst, das eine separate Hauptfunktion "main" enthält

#! python
# -*- coding: utf-8 -*-
# (c) 2024 Your name LGPL


def main():
    """Here is where the magic happens"""
    return

if __name__ == '__main__':
    # This will be true only if the file is "executed"
    # but not if imported as module
    main()

... zum Erstellen einer leeren Seite

Der Ablauf zur Erstellung einer Vorlage besteht aus den Schritten:

  • Den Volagenordner lokalisieren.
  • Eine Datei zum Schreiben zu öffnen und so als ersten Schritt eine leere SVG-Datei anzulegen, eine Kopfzeile zu schreiben und die Datei zu schließen.
  • Danach wiederholt die Datei zum Anhängen weiterer Segmente jeweils öffnen und nach erfolgtem Anhängen wieder schließen.

Das Makro besteht aus mehreren Funktionen, die von der Hauptfunktion "main()" aufgerufen werden.
Zusätzliche Funktionen können vor der EndSvg-Funktion eingefügt werden und die dazugehörigen Aufrufe werden vor dem EndSvg()-Aufruf eingesetzt.
Man muss auf die Anzahl der Leerzeichen achten, die mit den Schreiboperationen erzeugt werden, und damit auf den korrekten Zeileneinzug.

pathToTemplate()

Bevor überhaupt Kode erzeugt wird, benötigt man einen Ordner zum Speichern der neuen Vorlagen und es muss ein Dateiname vergeben werde.

Der Benutzer sollte bereits einen Vorlagenordner ausgewählt haben. Sein Pfad ist dann in den TechDraw-Voreinstellungen gespeichert.
Es ist nicht nötig zu wissen, wo die Voreinstellungen gespeichert werden, da FreeCAD Befehle enthält, mit denen man die benötigten Parameter direkt ansprechen kann.

def pathToTemplate(template_name):
    """Link a given template name to the path of the template folder"""
    #- Get the path to the template folder that is set in the FreeCAD parameters
    parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
    template_path = parameter_path.GetString("TemplateDir")
    #- Link template_path and template_name for any OS
    path_to_file = os.path.join(template_path, template_name)  # to join path segments OS neutral
    return path_to_file

parameter_path erhält den Pfad zum "Verzeichnis" innerhalb der Konfigurationsdatei, wo die Variable "TemplateDir" gefunden werden kann.
template_path erhält den Inhalt von "TemplateDir", also den Pfad zum Vorlagenverzeichnis.
template_name erhält den Namen der neu zu erstellenden Vorlage.

Nun muss der template_name noch so mit dem template_path verknüpft werden, dass das Ergebnis sowohl zu unix-basierten Betriebssystemen als auch zu Windows kompatibel ist.
Dies wird mit dem Befehl "os.path.join" erreicht und in der Variablen template_file gespeichert. Um diesen Befehl zu aktivieren, wird eine Importanweisung "import os" verlangt.

createSvgFile()

Dies erstellt eine neue Vorlage und speichert die XML-Kopfzeile.

def createSvgFile(file_path):
    # Create a file and insert a header line (with t as the space saving variant of template)
    t = open(file_path, "w")  # w = write, overwrites existing files
    t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
    t.close

startSvg()

Dies wird an die Vorlagendatei angefügt und erstellt den öffnenden SVG-Tag inklusive seiner Attribute.
Jede Schreibanweisung enthält eine einzelne SVG-Codezeile, die auf "\n" endet, dem Kennzeichen für CR/LF.

def startSvg(file_path, sheet_width, sheet_height):
    # Create svg-tag including namespace and format definitions
    t = open(file_path, "a", encoding="utf-8")
    # a = append, new lines are added at the end of an existing file
    # encoding="utf-8", helps with special characters if the Python interpreter is in ASCII mode
    t.write("\n" + "\n")
    t.write("<svg\n")
    #- Namespace declarations
    t.write("  xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
    t.write("  xmlns:freecad=\"http://www.freecad.org/wiki/index.php?title=Svg_Namespace\"\n")
    #- Format definition
    t.write("  width =\"" + sheet_width + "mm\"\n")
    t.write("  height=\"" + sheet_height + "mm\"\n")
    t.write("  viewBox=\"0 0 " + sheet_width + " " + sheet_height + "\">\n")
    # identical values for width and height and Viewbox' width and height
    # will synchronise mm and svg-units
    t.close

Funktion endSvg()

Dies wird wieder an die Vorlagendatei angefügt und erstellt den schließenden SVG-Tag; Damit wird der SVG-Code letztlich abgeschlossen.

def endSvg(file_path):
    # Create closing svg-tag
    t = open(file_path, "a", encoding="utf-8")
    t.write("</svg>")
    t.close

Funktion main()

Die Funktion main() ruft alle weiteren Funktionen auf und übergibt einige Parameter.

def main():
    """This one creates an empty A3 template"""
    #- Set the name of the template file and get its location
    template_file = pathToTemplate("MyTemplate.svg")  # Change the template name here
    #- Here starts the compiling of the svg file
    createSvgFile(template_file)  # overwrites existing File
    #- Set sheet format (DIN A3)
    format_width  = "420"
    format_height = "297"
    startSvg(template_file, format_width, format_height)  # adds svg start tag
    endSvg(template_file)  # adds svg end tag
    # At this point a new SVG-file is generated and saved
    return

In diesem Beispiel sind die Werte für format_width und format_height festgeschriebene (hard coded) Abmessungen, Die beiden eigentlich nicht notwendigen Zeilen markieren den Punkt an dem andere Funktionen zum Ermitteln der Formatdaten ihren Inhalt ablegen können.

Das komplette Makro für eine leere Seite

Dieses Makro enthält die obigen Codeabschnitte und ist startbereit.

#! python
# -*- coding: utf-8 -*-
# (c) 2024 Your name LGPL

import os  # to enable the use of os.path.join()


# - SVG creation -
def createSvgFile(file_path):
    # Create a file and insert a header line (with t as the space saving variant of template)
    t = open(file_path, "w")  # w = write, overwrites existing files
    t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
    t.close

def startSvg(file_path, sheet_width, sheet_height):
    # Create svg-tag including namespace and format definitions
    t = open(file_path, "a", encoding="utf-8")
    # a = append, new lines are added at the end of an existing file
    # encoding="utf-8", helps with special characters if the Python interpreter is in ASCII mode
    t.write("\n" + "\n")
    t.write("<svg\n")
    #- Namespace declarations
    t.write("  xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
    t.write("  xmlns:freecad=\"http://www.freecad.org/wiki/index.php?title=Svg_Namespace\"\n")
    #- Format definition
    t.write("  width =\"" + sheet_width + "mm\"\n")
    t.write("  height=\"" + sheet_height + "mm\"\n")
    t.write("  viewBox=\"0 0 " + sheet_width + " " + sheet_height + "\">\n")
    # identical values for width and height and Viewbox' width and height
    # will synchronise mm and svg-units
    t.close

def endSvg(file_path):
    # Create closing svg-tag
    t = open(file_path, "a", encoding="utf-8")
    t.write("</svg>")
    t.close

def pathToTemplate(template_name):
    """Link a given template name to the path of the template folder"""
    #- Get the path to the template folder that is set in the FreeCAD parameters
    parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
    template_path = parameter_path.GetString("TemplateDir")
    #- Link template_path and template_name for any OS
    path_to_file = os.path.join(template_path, template_name)  # to join path segments OS neutral
    return path_to_file

def main():
    """This one creates an empty A3 template"""
    #- Set the name of the template file and get its location
    template_file = pathToTemplate("MyTemplate.svg")  # Change the template name here
    #- Here starts the compiling of the svg file
    createSvgFile(template_file)  # overwrites existing File
    #- Set sheet format (DIN A3)
    format_width  = "420"
    format_height = "297"
    startSvg(template_file, format_width, format_height)  # adds svg start tag
    endSvg(template_file)  # adds svg end tag
    # At this point a new SVG-file is generated and saved
    return

if __name__ == '__main__':
    # This will be true only if the file is "executed"
    # but not if imported as module
    main()


... zum Erstellen einer Seite mit ein paar Strichen

Um aus einer leeren Seite eine Zeichnung zu machen, braucht man:

  • Rahmen, d.h. Rechtecke, die mit der Anweisung rect erstellt werden.
  • Ein Schriftfeld und noch etwas mehr, das aus Linien besteht, die mit der Anweisung path erstellt werden.
  • Einfache Texte für Indizes und die Benennungen der Schriftfeldzellen.
  • Editierbare Texte für z.B. Sachnummer und Benennung.

Diese graphischen Elemente werden normalerweise mehrfach benutzt, daher werden für die erzeugenden Kodes vier Funktionen erstellt:

  • svgRect() für rechteckige Rahmenelemente.
  • svgPath() für gerade Linienelemente.
  • svgText() für unveränderliche Texte.
  • ediText() für editierbares Texte.

Alle Funktionen werden innerhalb des obigen Makros vor der Funktion main() eingefügt und die dazugehörigen Funktionsaufrufe zwischen startSvg(...) und endSvg(...) in der Funktion main().

Funktion svgRect()

Um ein Rechteck zu zeichnen, muss die Funktion svgRect aufgerufen und die Werte für Breite, Höhe und die Position der linken oberen Ecke übergeben werden. Das liest sich an dieser Stelle besser als wenn svg_line die komplette SVG-Codezeile enthalten würde.

def svgRect(width, height, x, y):
    # Generates an svg-instruction to draw a rectangle with the given values
    svg_line = (
        "<rect width=\"" + width + "\" height=\"" + height + "\" x=\"" + x
        + "\" y=\"" + y + "\" />"
        )
    return svg_line

Die SVG-Anweisung ist geteilt, um innerhalb der empfohlenen Python-Zeilenlänge zu bleiben, sie ergibt trotzdem eine einzelne SVG-Zeile.

Funktion svgPath()

Um eine Linie zu zeichnen, muss die Funktion svgPath aufgerufen und die Koordinaten für Start- und Endpunkt einer Linie übergeben werden.

Anstatt der Endpunktkoordinaten kann ein Kennwert übergeben werden, um eine horizontale (h) oder vertikale (v) Linie zu zeichnen, gefolgt von der Länge der Linie, oder ein Kennwert, um eine Horizontale (H) oder vertikale (V) Linie zu zeichnen, gefolgt von der X- bzw. Y-Ordinate des Endpunktes.

Jeder Pfad beginnt am Ursprung und die erste Aktion ist eine Bewegung mit "angehobenem Stift" (nicht zeichnend) zum Startpunkt. In diesem Fall sind die relative und die absolute Bewegung identisch und daher ist es egal, ob der M-Tag ein großes oder ein kleines M enthält.

def svgPath(x1, y1, x2, y2):
    # Generates an svg-instruction to draw a path element (line) with the given values
    if x2 == "v" or x2 == "V" or x2 == "h" or x2 == "H":
        svg_line = ("<path d=\"m " + x1 + "," + y1 + " " + x2 + " " + y2 + "\" />")
    else:
        svg_line = ("<path d=\"m " + x1 + "," + y1 + " l " + x2 + "," + y2 + "\" />")
    return svg_line

Funktion svgText()

Um etwas Text zu zeichnen, muss die Funktion svgText aufgerufen und die Koordinaten für den Ankerpunkt des Textes, die Textzeichenkette selbst sowie wahlweise ein Winkel für gedrehten Text übergeben werden. Zum Drehen von Texten muss eine Transformationsanweisung in jeden einzelnen Text-Tag eingefügt werden; die Drehpunkte müssen dieselben Koordinaten erhalten, wie die zugehörigen Text-Ankerpunkte.

(Die Textausrichtung wird durch den umschließenden Gruppen-Tag gesteuert oder ist standardmäßig linksbündig)

def svgText(x, y, str_value, str_angle="0"):
    """
    Generates an svg-instruction to place a text element with the given values.
    Optional str_angle enables vertical and arbitrarily rotated texts
    """
    if str_angle == "0":
        svg_line = ("<text x=\"" + x + "\" y=\"" + y + "\">" + str_value + "</text>")
    else:
        svg_line = (
            "<text x=\"" + x + "\" y=\"" + y + "\" transform=\"rotate(" + str_angle
            + "," + x + "," + y + ")\">" + str_value + "</text>"
            )
    return svg_line

Funktion ediText()

Um einen editierbaren Text zu zeichnen, muss die Funktion ediText aufgerufen und die Koordinaten für den Ankerpunkt des Textes, die Textzeichenkette selbst sowie wahlweise ein Winkel für gedrehten Text übergeben werden. Zum Drehen von Texten muss eine Transformationsanweisung in jeden einzelnen Text-Tag eingefügt werden; die Drehpunkte müssen dieselben Koordinaten erhalten, wie die zugehörigen Text-Ankerpunkte.

FreeCAD erzeugt ein Dictionary-Objekt für jede hinzugefügte Vorlage, und jeder Eintrag hat einen Namen (key) und einen Wert (value).

(Die Textausrichtung wird durch den umschließenden Gruppen-Tag gesteuert oder ist standardmäßig linksbündig)

def ediText(entry_name, x, y, str_value, str_angle="0"):
    """
    Generates an svg-instruction to place an editable text element with the given values.
    Optional str_angle enables vertical and arbitrarily rotated editable texts
    """
    if str_angle == "0":
        svg_line = (
            "<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
            + "\">  <tspan>" + str_value + "</tspan>  </text>"
            )
    else:
        svg_line = (
            "<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
            + "\" transform=\"rotate(" + str_angle + "," + x + "," + y + ")\">  <tspan>"
            + str_value + "</tspan>  </text>"
            )
    return svg_line

Funktionstyp createXxxx

Diese Funktionen beginnen mit Kode zum öffnen einer Datei im Anfüge-Modus (append mode) sowie zum schreiben des öffnenden Gruppen-Tags.

Dann folgt ein Abschnitt zum setzen und berechnen von Werten und mit Schreibanweisungen, die die obigen Funktionen zum erstellen von SVG-Kode aufrufen.

Und am Ende steht der schließende Gruppen-Tag, gefolgt von einer Anweisung zum Schließen der Datei.

def createFrame(file_path, sheet_width, sheet_height):
    # Creates rectangles for sheet frame and drawing area
    t = open(file_path, "a", encoding="utf-8")
    t.write("    <g id=\"drawing-frame\"\n")
    t.write("      style=\"fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round\">\n")
    #- upper left corner of inner Frame, drawing area
    frame_x = str(20)
    frame_y = str(10)
    #- frame dimensions
    frame_width = str(int(sheet_width) - 20 - 10)
    frame_height = str(int(sheet_height) - 10 - 10)
    #- frame rectangle
    t.write("      " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
    #- upper left corner outer frame, sheet frame
    frame_x = str(15)
    frame_y = str(5)
    #- frame dimensions
    frame_width = str(int(sheet_width)-20)
    frame_height = str(int(sheet_height)-10)
    #- frame rectangle
    t.write("      " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
    t.write("    </g>\n\n")
    t.close

Das resultierende Makro

Dieses Makro fügt ein paar grundlegende graphische Elemente hinzu, die für richtige Vorlagen gebraucht werden, wie z.B. Linienelemente, Texte und editierbares Texte.

#! python
# -*- coding: utf-8 -*-
# (c) 2024 Your name LGPL

import os  # to enable the use of os.path.join()


# - SVG creation -
def createSvgFile(file_path):
    # Create a file and insert a header line (with t as the space saving variant of template)
    t = open(file_path, "w")  # w = write, overwrites existing files
    t.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
    t.close

def startSvg(file_path, sheet_width, sheet_height):
    # Create svg-tag including namespace and format definitions
    t = open(file_path, "a", encoding="utf-8")
    # a = append, new lines are added at the end of an existing file
    # encoding="utf-8", helps with special characters if the Python interpreter is in ASCII mode
    t.write("\n" + "\n")
    t.write("<svg\n")
    #- Namespace declarations
    t.write("  xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n")
    t.write("  xmlns:freecad=\"http://www.freecad.org/wiki/index.php?title=Svg_Namespace\"\n")
    #- Format definition
    t.write("  width =\"" + sheet_width + "mm\"\n")
    t.write("  height=\"" + sheet_height + "mm\"\n")
    t.write("  viewBox=\"0 0 " + sheet_width + " " + sheet_height + "\">\n")
    # identical values for width and height and Viewbox' width and height
    # will synchronise mm and svg-units
    t.close

def endSvg(file_path):
    # Create closing svg-tag
    t = open(file_path, "a", encoding="utf-8")
    t.write("</svg>")
    t.close

def svgRect(width, height, x, y):
    # Ggenerates an svg-instruction to draw a rectangle with the given values
    svg_line = (
        "<rect width=\"" + width + "\" height=\"" + height + "\" x=\"" + x
        + "\" y=\"" + y + "\" />"
        )
    return svg_line

def svgPath(x1, y1, x2, y2):
    # Generates an svg-instruction to draw a path element (line) with the given values
    if x2 == "v" or x2 == "V" or x2 == "h" or x2 == "H":
        svg_line = ("<path d=\"m " + x1 + "," + y1 + " " + x2 + " " + y2 + "\" />")
    else:
        svg_line = ("<path d=\"m " + x1 + "," + y1 + " l " + x2 + "," + y2 + "\" />")
    return svg_line

def svgText(x, y, str_value, str_angle="0"):
    """
    Generates an svg-instruction to place a text element with the given values.
    Optional str_angle enables vertical and arbitrarily rotated texts
    """
    if str_angle == "0":
        svg_line = ("<text x=\"" + x + "\" y=\"" + y + "\">" + str_value + "</text>")
    else:
        svg_line = (
            "<text x=\"" + x + "\" y=\"" + y + "\" transform=\"rotate(" + str_angle
            + "," + x + "," + y + ")\">" + str_value + "</text>"
            )
    return svg_line

def ediText(entry_name, x, y, str_value, str_angle="0"):
    """
    Generates an svg-instruction to place an editable text element with the given values.
    Optional str_angle enables vertical and arbitrarily rotated editable texts
    """
    if str_angle == "0":
        svg_line = (
            "<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
            + "\">  <tspan>" + str_value + "</tspan>  </text>"
            )
    else:
        svg_line = (
            "<text freecad:editable=\"" + entry_name + "\" x=\"" + x + "\" y=\"" + y
            + "\" transform=\"rotate(" + str_angle + "," + x + "," + y + ")\">  <tspan>"
            + str_value + "</tspan>  </text>"
            )
    return svg_line

def createFrame(file_path, sheet_width, sheet_height):
    # Creates rectangles for sheet frame and drawing area
    t = open(file_path, "a", encoding="utf-8")
    t.write("    <g id=\"drawing-frame\"\n")
    t.write("      style=\"fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round\">\n")
    #- calculate upper left corner of inner Frame, drawing area
    frame_x = str(20)
    frame_y = str(10)
    #- frame dimensions
    frame_width = str(int(sheet_width) - 20 - 10)
    frame_height = str(int(sheet_height) - 10 - 10)
    #- frame rectangle
    t.write("      " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
    #- calculate upper left corner outer frame, sheet frame
    frame_x = str(15)
    frame_y = str(5)
    #- frame dimensions
    frame_width = str(int(sheet_width)-20)
    frame_height = str(int(sheet_height)-10)
    #- frame rectangle
    t.write("      " + svgRect(frame_width, frame_height, frame_x, frame_y) + "\n")
    t.write("    </g>\n\n")
    t.close

def createTitleBlock(file_path, sheet_width, sheet_height):
    """Creates a title block and transfers it to the position according to the sheet dimensions"""
    #- calculate title block origin (lower left corner), offset from page origin
    tbX = str(int(sheet_width) - 10 - 180)  # 180 according to DIN EN ISO 7200
    tbY = str(int(sheet_height) - 10)
    t = open(file_path, "a", encoding="utf-8")
    #- group to transfer all included title block elements at once
    t.write("    <g id=\"titleblock\"\n")
    t.write("      transform=\"translate(" + tbX + "," + tbY + ")\">\n")
    #- sub-group of title block line framework
    t.write("      <g id=\"titleblock-frame\"\n")
    t.write("        style=\"fill:none;stroke:#000000;stroke-width:0.35;\
stroke-linecap:miter;stroke-miterlimit:4\">\n")
    t.write("        " + svgPath("  0","  0","  0","-63") + "\n")
    t.write("        " + svgPath("  0","-63","180","  0") + "\n")
    t.write("        " + svgPath("  0","-30","h","155") + "\n")
    t.write("        " + svgPath("155","  0","v","-63") + "\n")
    t.write("      </g>\n")
    #- sub-group of title block static texts (left-aligned by default)
    t.write("      <g id=\"titleblock-text-non-editable\"\n")
    t.write("        style=\"font-size:5.0;text-anchor:start;fill:#000000;\
font-family:osifont\">\n")
    t.write("        " + svgText("  4.5","-43.5 ","Some static text") + "\n")
    t.write("        " + svgText("  4.5","-13.5 ","More static text") + "\n")
    t.write("        " + svgText("162.5","-3.5 ","Vertical static text","-90") + "\n")
    t.write("      </g>\n")
    t.write("    </g>\n\n")
    t.close

def createEditableText(file_path, sheet_width, sheet_height):
    """Creates editable texts positioned according to the page origin"""
    #- calculate offset to titleblock origin
    edX = int(sheet_width) - 10 - 180 # 180 according to DIN EN ISO 7200
    edY = int(sheet_height) - 10
    t = open(file_path, "a", encoding="utf-8")
    #- group editable texts using the same attributes
    t.write("    <g id=\"titleblock-editable-texts\"\n")
    t.write("      style=\"font-size:7.0;text-anchor:start;fill:#0000d0;\
font-family:osifont\">\n")
    t.write(
        "      " + ediText("EdiText-1",str(edX + 60),str(edY - 43.5),"Some editable text") + "\n"
        )
    t.write(
        "      " + ediText("EdiText-2",str(edX + 60),str(edY - 13.5),"More editable text") + "\n"
        )
    t.write(
        "      " + ediText("EdiText-3",str(edX + 173),str(edY - 4.5),"90° editable text","-90")
        + "\n"
        )
    t.write("    </g>\n\n")
    t.close

def pathToTemplate(template_name):
    """Link a given template name to the path of the template folder"""
    #- Get the path to the template folder that is set in the FreeCAD parameters
    parameter_path = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/TechDraw/Files")
    template_path = parameter_path.GetString("TemplateDir")
    #- Link template_path and template_name for any OS
    path_to_file = os.path.join(template_path, template_name)  # to join path segments OS neutral
    return path_to_file

def main():
    """This one creates an A3 template with simple frame and title block"""
    #- Set the name of the template file and get its location
    template_file = pathToTemplate("MyTemplate.svg")  # Change the template name here
    #- Here starts the compiling of the svg file
    createSvgFile(template_file)  # overwrites existing File
    #- Set sheet format (DIN A3)
    format_width  = "420"
    format_height = "297"
    startSvg(template_file, format_width, format_height)  # adds svg start tag
    createFrame(template_file, format_width, format_height)
    createTitleBlock(template_file, format_width, format_height)
    createEditableText(template_file, format_width, format_height)
    endSvg(template_file)  # adds svg end tag
    # At this point a new SVG-file is generated and saved
    return

if __name__ == '__main__':
    # This will be true only if the file is "executed"
    # but not if imported as module
    main()

Und das ist der SVG-Kode aus diesem Makro:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<svg
  xmlns="http://www.w3.org/2000/svg" version="1.1"
  xmlns:freecad="http://www.freecad.org/wiki/index.php?title=Svg_Namespace"
  width ="420mm"
  height="297mm"
  viewBox="0 0 420 297">
    <g id="drawing-frame"
      style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round">
      <rect width="390" height="277" x="20" y="10" />
      <rect width="400" height="287" x="15" y="5" />
    </g>

    <g id="titleblock"
      transform="translate(230,287)">
      <g id="titleblock-frame"
        style="fill:none;stroke:#000000;stroke-width:0.35;stroke-linecap:miter;stroke-miterlimit:4">
        <path d="m   0,  0 l   0,-63" />
        <path d="m   0,-63 l 180,  0" />
        <path d="m   0,-30 h 155" />
        <path d="m 155,  0 v -63" />
      </g>
      <g id="titleblock-text-non-editable"
        style="font-size:5.0;text-anchor:start;fill:#000000;font-family:osifont">
        <text x="  4.5" y="-43.5 ">Some static text</text>
        <text x="  4.5" y="-13.5 ">More static text</text>
        <text x="162.5" y="-3.5 " transform="rotate(-90,162.5,-3.5 )">Vertical static text</text>
      </g>
    </g>

    <g id="titleblock-editable-texts"
      style="font-size:7.0;text-anchor:start;fill:#0000d0;font-family:osifont">
      <text freecad:editable="EdiText-1" x="290" y="243.5">  <tspan>Some editable text</tspan>  </text>
      <text freecad:editable="EdiText-2" x="290" y="273.5">  <tspan>More editable text</tspan>  </text>
      <text freecad:editable="EdiText-3" x="403" y="282.5" transform="rotate(-90,403,282.5)">  <tspan>90° editable text</tspan>  </text>
    </g>

</svg>

Und so soll es aussehen, wenn sie (die Vorlage) eingefügt wurde (mit vergrößertem Schriftfeld):

Die editierbaren Texte in blau einzufärben ist nur eine persönliche Vorliebe, um unveränderlichen und editierbaren Text einfacher unterscheiden zu können.