Hauptseite

Blog

Povray

Povray-OS

Balkonien

Impressum

 

Povray - Open Source --- MA_Helpers_V1


Veröffentlicht: 05.05.2019
Update: 13.05.2019

In Povray fehlen enorm viele Funktionen, die man in anderen Programmiersprachen für selbstverständlich hält. Arrays sortieren und auseinandernehmen, Strings bearbeiten, Typenumwandlungen, liebliche RegExe, für all das gibt es in PHP an die hundert Funktionen.

Povray dagegen bietet für die Behandlung von Strings nur zwei hilfreiche Befehle:
substr() und strlen(). Das wars. Vielleicht noch concat(), aber das war es dann wirklich.

Da diese für meine Zwecke bei Weitem nicht ausreichen, sind über die Jahre einige Makros zusammengekommen. Hat enorm Zeit gekostet, vielleicht kann ich mit deren Veröffentlichung dem ein- oder anderen viel Zeit und Mühe ersparen.

Darüberhinaus enthält sie reparierte Funktionen, die in Povray direkt oder als Skript-Makros im Standard-Paket vorliegen und Fehler beherbergen.

Ohne ma_helpers_v*.inc läuft keines meiner größeren Pakete!

Die Datei muss überall inkludiert werden, ansonsten hagelt es Fehlermeldungen. Trotz ihrer Größe von bald 2500 Zeilen (Mai 2019) kratzt das die Performance nicht, es dürfte kaum ein Sekündchen Renderzeit obendrauf kommen. Es sei denn in der Render-Box schwitzt ein 4-86er.

Für genaue Dokumentationen der Makros, Parameter und was am Ende bei rauskommt, siehe Quellcode.

Variablen
Im oberen Bereich der Datei sind Dutzende Variablen hinterlegt, mit denen die Funktionsweisen der Makros beeinflusst werden können. Bevor irgendwer ein Makro kopiert, umbenennt und für seine Zwecke abändert, sollte ein Blick auf die Variablen geworfen werden, ob der liebe Mä vorausschauend gearbeitet hat.
Desweiteren wird von einigen meiner Skripte die Verwendung mancher Variablen vorausgesetzt.

Cam* und Lig*
Betrifft folgende Variablen:
CamLocation, CamLookAt, CamAngle, CamRotate, CamSky, CamType, LigVector, LigColor, LigIntensity, LigRotate

Deren Inhalte werden vom Makro MA_CameraAndLight_Set() in die Tat umgesetzt. Auf diese Weise können einzelne Angaben bequem geändert werden, ohne den ganzen camera{}-Aufruf jedesmal zu erneuern. Oder die Werte werden mehrmals überschrieben, ohne das Povray wegen zuvieler Kameras in der Szene meckert. Ebenfalls möglich werden dazu einige Spielereien wie z.B. Licht langsam ein- und ausblenden.

Simples Beispiel:
#declare CamAngle = 60;
doSomething()
#declare CamRotate = <10,0,0>;
MA_CameraAndLight_Set()

Debug und Logfile

MA_Terminal   ( mText )
MA_Terminal_* ( mText, mValue )
MA_Terminal() schickt einen Text ans Terminal.
Soll eine Variable mit ausgegeben werden, steht für jeden Variablen-Typ eine entsprechende Funktion bereit.
Z.B. MA_Terminal_S(...) für Strings, MA_Terminal_F(...) für Float, *A für Array und *V für Vektoren.
Eignet sich in aufwendigeren Skripten gut, um fix den Inhalt von berechneten Variablen auszugeben und zu überprüfen.

MA_Error ( mMacro, mText )
Sendet eine Error-Meldung an das Terminal und löst dann einen echten #error per Povray-Direktive aus.

MA_Warning ( mMacro, mText )
Sendet eine Warnung an das Terminal.

MA_Notice ( mMacro, mText )
Sendet eine simple Notice an das Terminal.
Ist MA_Terminal_S sehr ähnlich, allerdings entsprechen Parameter und Ausgabe mehr denen der Warnings und Errors.

MA_Deprecated ( mOldMacro, mNewMacro )
Es kommt bei mir häufiger vor, das ich Makros umbenenne oder sich Parameter ändern. Tut man jenes, laufen ältere Skripte vielleicht nicht mehr oder man muss sofort und überall alles ändern - beides wenig beglückend. Daher lasse ich das alte Makro meist noch ein Weilchen bestehen, leite auf das neue um und sende einen nervig-langen Text ans Terminal, der mir im Terminal die übersichtliche Formatierung durcheinanderwirft.
Lieber meckern als mit einem Error abstürzen. Beispiel:
#macro Old_Macro  (mVar1, mVar2)
	MA_Deprecated ("Old_Macro", "New_Macro")
	New_Macro (mVar1, mVar2 + 4711)
#end

Ausgabe im Terminal:
*** MA => *** DEPRECATED ***: Macro 'Old_Macro' is old and risky, use 'New_Macro' instead!!!

MA_Finish ()
Sendet Statistik-Daten an das Terminal. Bisher nur über die Anzahl an Warnungen und Deprecated-Nachrichten.
Sollte - wenn überhaupt - ganz am Ende der ausführenden Povray-Datei verwendet werden.

Wichtiger Hinweis
Ich mache den Umweg über diese Makro-Sammlung, damit ich entsprechende Texte irgendwann in ein Logfile speichern kann. Auf diese Weise brauche ich nur diese Makros anzupassen, nicht sämtliche Aufrufe in zig dutzend Dateien.

Nachteil dieser Vorgehensweise ist ganz klar, das keine ordentlichen Zeilennummern mit ausgegeben werden (können). Wer mit Fehlermeldung oder Warnung gleich über Datei und Zeile informiert werden möchte, sollte meine Makros vergessen und gleich die entsprechenden Direktiven #debug, #warning und #error verwenden.

Siehe: http://povray.org/documentation/3.7.0/r3_3.html#r3_3_2_7_1

Spezialitäten
MA_CameraAndLight_Prepare ()
Bereitet alle Variablen rund um Kamera- und Lichtverarbeitung vor.
Sollte direkt nach jeder Änderung an Kamera- oder Lichteinstellungen aufgerufen werden.

MA_CameraAndLight_Set ()
Verwendet die Werte für Kamera und Lichtquelle, die in den globalen Variablen Cam* und Lig* gespeichert sind und setzt beides mit camera{} und light_source{}. In Verbindung mit ein paar Vorgaben und anderen Zeilen lassen sich damit nette Dinge realisieren, deren Anwendungsbeispiele erst noch zusammenstricken werden wollen.
Braucht nur einmal ganz am Ende eines Skripts aufgerufen werden, als eine der letzten Zeilen.

Ein Beispiel, wie die beiden Funktionen zu verwenden sind, ist in Arbeit.

MA_RenderStyle_Set ( mStyle )
Erlaubt es, Objekte bei der Erstellung auf verschiedene Qualitätsstufen herunterzuschrauben. Ich verwende RS in möglichst vielen Objekten, um die Render-Zeiten während der Entwicklung niedrig zu halten. Während der Entwicklung komplexer Objekte muss nicht alles spiegeln, blinken und jede kleine Schraube Schatten werfen, das Rendern dauert auch ohne häufig lange genug.

Ein kleines Anwendungsbeispiel für das Arbeiten mit den RS-Angaben:
sphere { 0,1
	texture {
		#if (RS_Dev)	pidgment { color Gray10 }	#end
		#if (RS_Tec)	pidgment { color Green }	#end
		#if (RS_Real)	pidgment { color Silver }	#end
		#if (RS_Hell)	pidgment { color Silver2 }	#end
	}
}

MA_RenderStyle_Reset ()
Setzt den RenderStyle (RS) auf den letzten Wert zurück. Will man nur ein Objekt in höchster Detailtiefe erzeugen, wird RS generell auf einen niedrigeren Wert gesetzt, kurz vor Erzeugung des gewünschen Objektes hoch auf höchste Stufe, und danach per Reset wieder hinunter. Das kann ebenso innerhalb der Objekte passieren, denen es in dem Fall egal sein kann, auf welchem Wert RS vorher stand.

MA_RenderStyle_Set("real") // or "dev", whatever.
object { Get_far_away_object__No_need_to_be_too_detailed() }
MA_RenderStyle_Set("hell")
object { Get_something_very_near_with_all_possible_details() }
MA_RenderStyle_Reset()
object { Get_another_far_away_object() }

MA_GetParam* ( xParams, xParamName, mDefault ) => float/string/vector/boolean
MA_GetParam_S ( xParams, xParamName, mDefault ) => string
MA_GetParam_F ( xParams, xParamName, mDefault ) => float
MA_GetParam_V ( xParams, xParamName, mDefault ) => vector
MA_GetParam_B ( xParams, xParamName ) => boolean

Povray kennt weder optionale Parameter noch andere, halbwegs kontrollierbare Möglichkeiten, um eine variable Anzahl Variablen/Parameter an Makros zu übergeben. Das Problem lösen die Methoden MA_GetParam_*() rustikal aber wirksam.

Ähnlich wie bei CSS-Angaben werden Parameter und Wert mit Doppelpunkt voneinander getrennt. Die so entstandenen Paare von anderen Paaren mit Semikolon getrennt und alles zusammen in einem String gespeichert. Der String wird an die verschiedenen Makros mitsamt einem Parameter-Namen übergeben, dessen Wert - sofern vorhanden - zurückgegeben wird. Besonders wenn Objekte versteckt innerhalb von Makros und Sub-Makros erzeugt werden und man an die eigentlichen Aufrufe von außen gar nicht mehr drankommt, ist diese Methode sehr hilfreich, um Parameter dezent durchzuschleifen.

In ma_helpers ist ein komplizierteres Beispiel ganz unten hinterlegt und meine Tussi muss sich gewaltig damit herumschlagen.
Hier an der Stelle sei nur die kurze Handhabung erwähnt:
#declare Params = "position: <0,1.5,0>; color: <0,0,1>; scale-y: 1.5; text: - povray -; reflection";
#macro ShowSphere (mParams) 
	#local mPos         = MA_GetParam_V(mParams, "position",   <0,5,0>  );
	#local mColor       = MA_GetParam_V(mParams, "color",      <0,0,0>  );
	#local mScaleY      = MA_GetParam_F(mParams, "scale-y",    1        );
	#local mText        = MA_GetParam_S(mParams, "text",       "-none-" );
	#local mReflection  = MA_GetParam_B(mParams, "reflection"           );
	#ifndef( Shapes_Temp) #include "shapes.inc" #end
	union {
		sphere { 0, 1
			texture { pigment { color mColor } }
			#if (mReflection) finish { reflection 0.1 phong 1} #end
		}
		object {
			Circle_Text_Valigned (
				"timrom.ttf", mText, 0.5, 0.0005, 0.05, 1, 1, Align_Center, -90, -90
			)     
			texture {	pigment{ color rgb <1,0,0> }	}
			rotate<90,0,0>
		}
		scale <1, mScaleY, 1> translate mPos
	}
#end
ShowSphere (Params)

MA_GetSlope ( xValue, mType, mIterations, mOffset ) => float

Gibt verschiedene Arten von Steigungen zurück. In Animationen rundet es Bewegungen ab, die bei linearen Veränderungen unnatürlich aussehen. Zum Beispiel das Öffnen einer Tür, ein Ball der auf den Boden titscht, ein beschleunigendes Auto.

Der Wert für Value wird von 0 bis 1 angegeben und entsprechend des Type verändert. Ein Cosinus z.B. steigt anfangs langsamer an als Linear, überholt Linear bei 0.5 und bremst gegen 1 gemächlich ab. Der Rückgabewert liegt dabei immer zwischen 0 und 1, entsprechend kann und sollte er als Multiplikator in Bewegungen oder Skalierungen dienen.

Das passiert für Iteration = 1 einmal, es wird raufgezählt von 0 bis 1. Soll es in derselben Manier wieder runter bis auf 0 gehen, ist Iteration = 2 anzugeben, für zweimal rauf und einmal runter "3", und so weiter.

Mit Offset kann der Startpunkt verschoben werden, Werte von 0 bis 2 sind sinnvoll. Offset = 0 beginnt unten, Offset = 1 oben, Offset = 0.5 mehr oder weniger in der Mitte (je nach Type), aber auf jedenfall auf dem Weg nach oben.

Rechts unten die derzeit verfügbaren Angaben für Type, die Farben entsprechen denen im Bild und der Animation. Im Bild wurde Iterations = 4 gesetzt, bei der Animation auf 2. Mit Ausnahme der beiden linearen, grauen Kugeln ganz rechs, die bewegen sich mit Iteration = 1 bzw. 4 und Offset = 1 bzw. 0.5. Der Quellcode der Animation wird als Example unten in der Include-Datei mitgeliefert (slope-2).

Sicherlich werden mit der Zeit weitere Slope-Typen hinzukommen, auf Anhieb fiele mir noch eine Step-Variante ein, die z.B. in zehntel-Sprüngen von 0,0 auf 0,1 hin zu 0,2 usw. springt. Nächste Version vielleicht.

MA_GetSlope_ByString  ( xValue, mTypeMixed ) => float

switch
linear
cosinus
cosinus+
cosinus++
bounce+
bounce-

Bietet dieselbe Funktionalität wie MA_GetSlope(), allerdings werden die drei Slope-Parameter in einem Komma- oder Semikolon-separierten String übergeben. Optionale Parameter gleich eingebaut, Iterations und Offset können weggelassen werden, getrimmt wird ohnehin wo geht.
MA_Terminal_F ("slope-1", MA_GetSlope_ByString(0.7, "linear,1,0"))
MA_Terminal_F ("slope-2", MA_GetSlope_ByString(0.7, "cosinus+;4;-0.5"))
MA_Terminal_F ("slope-3", MA_GetSlope_ByString(0.7, "bounce+"))         //bounce+,1,0
#local mySlope = MA_GetSlope_ByString(0.7, "");                         //linear, 1,0

Diese Funktion wird wahrscheinlich niemand in seinen Skripten direkt verwenden, da sie weniger leserlich ist. Gewissermaßen muss ich sie in meinen Screenplay-Makros verwenden, speziell der Matrix-Variante, da in der Matrix allemöglichen Angaben als String übergeben werden und diese dann nicht an mehreren Orten auseinandergepflückt werden müssen.

MA_SetClock ( mValue )
Belegt die globale Variable MA_Clock entweder mit dem Wert der Povray-Konstante clock (wenn clock_on = true), oder dem mitgeliefertem Value. Anschließend kann gemütlich mit MA_Clock animiert bzw. ein Standbild erzeugt werden. Im Endeffekt ersetzt die Verwendung dieses Makros nur ein paar IF-Blöcke.

Überflüssig:
#if (clock_on = false)
	#local myClock = 0.5;
#else
	#local myClock = clock;
#end
object { sphere { 0,1 } translate <0, 5 * myClock, 0> }

Stattdessen verwenden:
MA_SetClock(0.5)
object { sphere { 0,1 } translate <0, 5 * MA_Clock, 0> }

MA_OutputArrayString      ( mArray, xFile, mOverwrite, mComment )
MA_OutputArrayString_Base ( mArray, xFile, mOverwrite, mComment, mHighestIndex )

Speichert ein String-Array hübsch formatiert in einer Textdatei ab. Sehr nett, will man zweifelnderweis wissen, was dadrin wirklich los ist.
Dabei können mehrere Arrays in einer Textdatei abgelegt werden, setzt man Overwrite beim ersten Mal auf true und danach stetig auf false. Der Comment wird als Text über dem Array platziert und hilft bei der Identifizierung. Entsprechend sind Vorher/Nachher-Vergleiche auch größerer Kameraden gut möglich (und für nichts anderes war es zu Anfang gedacht).

MA_Include ( mFile, mBasePath )
Versucht die angegebene Datei mFile zu includen. Diese wird in den Pfaden gesucht, die in der globalen Variable MA_Include_Folders (ganz oben in der Datei) als Array angegeben sind. Der angegebene mBasePath dient dabei als Startpunkt. Falls es derselbe Ordner ist, in dem das ausgeführte Povray-File liegt, mBasePath = "" setzen, ansonsten den Slash am Ende nicht vergessen ("subfolder/").
Bei mir liegen die gesuchten Dateien immer im Ordner der ausgeführten Datei, in dem darunter "../" oder einem Speziellen darüber. Ich war das Auskommentieren von Include-Anweisungen für Testzwecke irgendwann leid.

Strings

MA_String_Pad ( xString, mLength, xPadChar, mPadPos ) => string
Fügt vorne oder hinten Zeichen ein, bis die gewünschte Länge erreicht ist.
#local Test = MA_String_Pad("Bongo", 10, "-", "l");     // returns "-----Bongo"
#local Test = MA_String_Pad("Bongo",  8, "-", "right"); // returns "Bongo---"

MA_String_Trim ( mParam ) => string
Entfernt vorne und hinten sämtliche Leerzeichen.

MA_String_TrimChar ( mParam, mChar ) => string
Entfernt vorne und hinten alle vorkommen des angegebenen Zeichens Char.

Arrays

MA_Array_FindString ( mString, mArray ) => boolean
Sucht in dem angegebenen Array nach einem String. Wird dieser gefunden, wird ein true zurückgegeben, ansonsten ein false.
Ich verwende dieses Makro fast ausschließlich dazu, um irsinnig ver-undete If-Abfragen zu umgehen.

#local City = "cologne";
#if (MA_Array_FindString(City, array[3] { "Bielefeld", "Cologne", "Posemuckel" }))
	MA_Terminal_T ("Yes man")
#end
// or ...
#if (!MA_Array_FindString(City, array[3] { "Bielefeld", "Cologne", "Posemuckel" })) ...

MA_Array_Merge ( mArray1, mArray2 ) => array
Verbindet zwei eindimensionale Arrays miteinander. Da die Inhalte nur kopiert und nicht untersucht/sortiert/angepackt werden, ist das Makro quer über alle Datentypen verwendbar.

MA_Array_Cut ( mArray, mIndexFirst, mIndexLast, mRows ) => array
Beschneidet eindimensionale Arrays, egal welche Datentypen es enthält. Dabei muss entweder IndexFirst und IndexLast angegeben werden, oder IndexFirst und die Anzahl der Rows (in dem Fall IndexLast = 0 setzen).

MA_Array2D_Merge( xArray1, xArray2 ) => 2d-array

Verbindet zweidimensionale Arrays miteinander, ohne sie zu sortieren.
Auch dieses Makro kann alle Datentypen verkraften.

MA_ArrayString2D_Sort( mArrayOld, mColSort, xDirection, xCompType ) => 2d-array
Sortiert zweidimensionale String-Arrays, nach Angabe der Spalte per ColSort als Array-Index (0-x), der Sortierreihenfolge Direction ("asc" oder "desc") und dem Vergleichstyp CompType ("string" oder "float"). Es können zwar nur String-Arrays sortiert werden, trotzdem können zu Vergleichszwecken sämtliche Felder auf floatige Art verglichen werden, was zu gänzlich anderen Resultaten führen kann (als String ist die "10" kleiner als die "2").

MA_ArrayString2D_MergeAndSort( xArray1, xArray2, mColSort, xDirection, xCompType ) => 2d-array
Eine simple Aneinanderreihung beider oben genannter 2D-Makros. Erst wird gemergt und dann gesortet.

Umwandlungen

MA_String2Array ( mString, mSep, mTrim ) => array
Wandelt einen String Anhand des Separators Sep in ein String-Array um.

MA_ArrayString2String ( mArray, mSep ) => string
Wandelt ein Array aus Strings in einen per Sep separierten String um.

MA_ArrayFloat2String ( mArray, mSep ) => string
Wandelt ein Array aus Floats in einen per Sep separierten String um.

MA_String2Vector ( mString ) => vector
Wandelt einen komma-separierten String in einen Vektor um, eventuelle Kleiner- und Größerzeichen werden getrimmt.

MA_ArrayString2Vector ( mArray ) => vector
Wandelt ein String-Array in einen Vektor um.

MA_Vector2String      ( mVector ) => string
MA_Vector2String_Base ( mVector, mDimensions, mDigits ) => string
Wandeln Vektoren in Strings um. MA_Vector2String() verwendet festgesetzte Parameter für Dimensions und Digits (Nachkommastellen), die mit MA_Vector2String_Base() manuell angegeben werden können.

MA_Deg2Vector*  ( mRA, mDE, mRadius ) => vector

Sternenhimmel mit real existierenden Sternen
MA_Deg2Vector00 ( mRA, mDE, mRadius ) => vector
MA_Deg2Vector10 ( mRA, mDE, mRadius ) => vector
MA_Deg2Vector01 ( mRA, mDE, mRadius ) => vector
MA_Deg2Vector11 ( mRA, mDE, mRadius ) => vector

Wandeln Rektaszension RA und Deklination DE in Verbindung mit der gewünschten Entfernung Radius in Vektoren um. Im Normalfall werden die Zahlenangaben für RA von -180 bis 180 und DE von -90 bis 90 Grad angegeben, was je nach Herkunft der Daten jedoch anders sein kann. Werden nur positive Werte für RA und DE geliefert, ist die Funktion MA_Deg2Vector00() zu verwenden. Bei den anderen Funktionen stehen die beiden Ziffern dafür, ob RA bzw. DE vom Makro in den positiven Bereich verschoben werden sollen, sprich ob zu RA-Werten 180 und/oder zu DE 90 addiert wird.
Intern wird mit Radianten gerechnet, die nur positive Werte von 0 bis 2 Pi vertragen.

MA_Vector2Deg ( mVector ) => array
DAS MAKRO FEHLT MIR NOCH !!!
Wenn mir irgendwer sagen kann, wie man für Folgendes die Umkehrfunktion schreibt, wäre ich sehr dankbar.

#macro MA_Deg2Vector  (mRA, mDE, mRadius)
	#local mRect	= radians(mRA + MA_Deg2Vector_RA_Offset);
	#local mDecl	= radians(mDE);
	//Um die folgenden drei Zeilen geht es.
	//Wäre evtl sogar einfach, ich hab mir Versuche bisher verkniffen.
	#local mPosX	= mRadius * sin(mDecl) * cos (mRect);
	#local mPosY	= mRadius * cos(mDecl);
	#local mPosZ	= mRadius * sin(mDecl) * sin (mRect);
	#local mVector	= <mPosX, mPosY * -1, mPosZ>;
	mVector
#end

Reparierte oder ergänzte Povray-Makros

MA_Circle_Text_Valigned ( ... ) => object
Das Makro Circle_Text_Valigned gibt es bereits in der povray-eigenen Include-Datei shapes.inc, sie erhält jedoch einen Fehler. Bei bestimmten Einstellungen steht der Text zu weit vom eigentlichen Objekt ab, was ich in meiner Version korrigiert habe.

Da ich mir nicht ganz sicher bin, ob das ein Bug, Feature oder nur eine Fehlverwendung von mir ist, habe ich das alte Makro nicht überschrieben sondern meine namensähnliche Kopie hinzugefügt. Parameter und Rückgabe sind ansonsten gleich, nur das dem Makro-Namen ein MA_ vorangesetzt und eine Zeile im Makro verändert wurde.

Siehe auch Shapes.inc in der Povray-Dokumentation.

Simples Beispiel:
union {
	cylinder {
		<0,0,0>, <0,2,0> 1
		texture { pigment { color rgb <1,1,0> transmit 0.2 } }
	}
	object {
		//Circle_Text_Valigned (
		MA_Circle_Text_Valigned (
			"timrom.ttf", "huhu", 0.5, 0.0005, 0.05, 1, 1, Align_Center, -90, -90
		)
		texture { pigment { color rgb <0.5,0,0> }	}
		rotate<90,0,0>
		translate <0,1,0>
	}
}

Experimentelles / Entwicklung
Für die folgenden Makros wird keine Garantie übernommen, das sie in der Form lange bestehen.
Verwendung auf eigenes Risiko.

MA_Testarea      ( mAll )
MA_Testarea_Base ( mXN, mXP, mZN, mZP )

Zeigt eine Schachbrett-ähnliche Testumgebung.
Die Parameter geben die Anzahl der Kacheln in den verschiedenen Richtungen an (X-Achse negativ, X-Achse positiv, dasselbe für Z). Wobei eine Kachel genau mittig unter <0,0,0> liegt und das ganze Feld somit um einen Unit breiter ist als die Summe der angegebenen Werte.
Unter den Ganzzahlen befindet sich daher das Kachel-Innere, nicht die Grenze von einer zur nächsten.

MA_CenterMarker      ( mLengthAxes )
MA_CenterMarker_Base ( mRadiusSphere, mThicknessAxes, mLengthAxes, mAxes )

Markiert <0,0,0> mit einer Kugel und die Achsen mit Linien.
Die Länge der Achsen wird intern um 0.5 erhöht, damit sie bündig mit den Kacheln der Testarea liegen.

Updates
13. Mai 2019 - Version 1.1
MA_GetParam* => Strings und erstellte Arrays werden gecached, um die Ausführung zu beschleunigen.
MA_Notice => neu hinzugefügt.
MA_Circle_Text_Valigned => neu hinzugefügt.

5. Mai 2019 - Version 1.0
Erste Version veröffentlicht.

Download
ma_helpers_v1.7z => 300 kB 7-Zip, Version 1.1 vom 13. Mai 2019

Kommentare

Keine Kommentare bisher.
* Die Angabe von E-mail und Webseite optional.
Die E-Mail wird nicht mit dem Kommentartext angezeigt sondern nur intern gespeichert, sollte lediglich bei Kontaktwunsch angegeben werden.
der deutschen Seite
der englischen Seite
beiden Seiten



© Mä 2014-2047