CNC Teil5 - Makros

14 Minuten Lesezeit

Dies ist der fünfte Teil meiner Serie über Portalfräsmaschinen. Er befasst sich mit praktischem Zubehör, welches das Leben als CNC-Bediener erleichtert und zu schnelleren und genaueren Fräsergebnissen führen kann.

Übersicht

In diesem Artikel geht es um Makros für die CNC, mit deren Hilfe sich wiederholende Vorgänge automatisiert werden können. Ich decke sowohl grundlegende als auch anspruchsvollere Makros für mehrere Zwecke wie automatische Z-Nullstellung, Werkzeuglängenmessung oder Werkzeugwechsel ab. Ich führe ein paar G-Code Befehle ein. Die Beschäftigung damit half mir dabei, ein tieferes Verständnis der Funktionsweise zu erarbeiten und damit “Vertrauen” zu meiner Maschine aufzubauen.

Haftungsausschluss

Die folgenden Subroutinen wurden für den RS274 NGC Interpreter geschrieben. Ich habe sie mit EdingCNC validiert, da dies die von mir verwendete CNC-Software ist. Ich habe einige Aspekte von Makro-Dateien abgeleitet, die sowohl Sorotec als auch Eding zusammen mit ihren Maschinen ausliefern.

⚠️ Ich kann für die Funktion des hier beschriebenen Codes auf anderen Maschinen keine Verantwortung übernehmen. Meine Absicht ist lediglich zu erklären wie die Makros funktionieren.

Was ist ein Makro?

Ein Makro oder Unterprogramm ist eine Sammlung von Anweisungen, die für den CNC-Interpreter geschrieben wurden, um Aktionen auszuführen. Sie werden oft verwendet, um wiederkehrende Vorgänge zu automatisieren. Auf meiner Maschine beginnt ein solches Makro mit einem SUB-Befehl und endet mit einem ENDSUB. Makros enthalten oft Verzweigungslogik und G-Code-Befehle, beispielsweise um die Maschine Bewegungen ausführen zu lassen, Parameter abzuspeichern oder Benutzerdialoge anzuzeigen.

Auf meiner Maschine befinden sich alle Makros in einer einzigen Datei namens macro.cnc, die von meiner CNC-Software beim Start eingelesen wird. Einige “Soft-Buttons” der Software rufen direkt Makros aus dieser Datei auf, z.B. für die Referenzfahrt.

Image: Macro execution in action Als kleine Einführung öffne ich das Dokument macro.cnc mit einem Texteditor. Ich suche nach Sub user_9 (es sollte eine Routine sein, die nichts Relevantes tut), entferne ihren Inhalt und füge stattdessen MSG "hello world!" ein. Abspeichern, CNC-Software öffnen, die Taste 9 im user menu drücken und Voilà!

Ein einfaches Makro zur Erkennung des Status des Werkzeuglängensensors

Legen wir los und verbinden das neu erworbene Wissen über Makros mit einem nützlichen Beispiel.

Der Werkzeuglängensensor

Image: Mein Werkzeuglängensensor Ein Werkzeuglängensensor (im Folgenden WZLS) ist ein Zubehörteil, das an einen Eingang der CNC-Maschine angeschlossen wird und bei Berührung (z.B. durch eine Werkzeugspitze) auslöst. Es gibt viele verschiedene Ausführungen, von einem einfachen Mikroschalter mit Gehäuse und Tastmechanismus bis hin zu hochpräzisen induktiven Sensoren aus feingedrehtem Edelstahl mit automatischer Luftspülung um sicherzustellen, dass die Oberfläche des Sensors sauber ist.

Der Werkzeuglängensensor wird entweder am Maschinenbett, auf dem Ablagetisch oder einfach an einer anderen festen Position an der Maschine angebracht. Meiner ist ein sehr einfacher Schalter. Seine Wiederholgenauigkeit ist mit 10µm angegeben.

Makro-Aufgabe

Das Makro soll vor der Messung prüfen, ob der WZLS betriebsbereit ist. Ich teste also, ob der Sensorschalter den Status “nicht ausgelöst” hat. Seine Verdrahtung ist als “normally closed” (NC) ausgeführt. Falls er doch ausgelöst ist (seine Kontakte also offen sind), könnte das Folgendes bedeuten:

  • Der Sensor klemmt in der ausgelösten Position
  • Kabelbruch in der Zuleitung zum WZLS
  • Sensor ist nicht mit der Maschine verbunden

Code: Prüfe Betriebsbereitschaft WZLS

SUB is_sensor_ok
    IF [sensorStatus == triggered] ;# 5068 == 0 (normally closed)
        DLGMSG "Tool length sensor not connected or already triggered - please check"
        IF [dialogButton == 1]
            IF [sensorStatus == triggered]
                ERRMSG "Tool length sensor input still unexpected - aborting"
            ENDIF
        ELSE
            ERRMSG "User abort."
        ENDIF
    ENDIF
ENDSUB

Der Name des Makros lautet is_sensor_ok. Ich prüfe den Sensorstatus und zeige eine Dialogmeldung an, falls hier etwas nicht stimmt. Sobald auf “OK” gedrückt wird, gehe ich davon aus, dass das Problem behoben wurde und versuche es erneut. Wenn er immer noch ausgelöst ist, breche ich die Routine ab.

Dieses Prüf-Makro ist in allen folgenden Abschnitten zu finden.

Benennung von Variablen

Die RS274/NGC-Sprache ist alt. Wirklich alt. Ihre erste Version wurde in den späten 1950er Jahren veröffentlicht. Kein Wunder, dass die Parameter (#1 - #5399 ist der erlaubte Bereich) alle numerisch sind. Sowohl in der Benennung als auch in den Werten, die sie speichern können. Es gibt kein Typsystem (wie Strings, int usw.) und keine praktischen Aliase oder menschenlesbare Namen, die das Verständnis eines Parameters wie sensorStatus erleichtert.

Im Makro auf der Maschine wird stattdessen #5068 erscheinen man muss sich selbst daran erinnern, dass der Hersteller der CNC-Software such auf diese Variable festgelegt hat, um den Status des Werkzeuglängensensors zu kennzeichnen. Und dass sie vom Typ boolean ist mit 0 = nicht ausgelöst und 1 = ausgelöst.

Um den Makrocode so lesbar und verständlich wie möglich zu gestalten, verzichte ich in diesem Artikel auf die Verwendung von numerischen Parameternamen. Eine Übersetzungstabelle ist ganz unten zu finden.

Verwendung des Werkzeuglängensensors zur Ermittlung der Werkstückhöhe (Z=0)

Hiermit kann die Werkstückhöhe automatisch eingemessen und auf der Obefläche abgenullt werden. So entfallen das manuelle Absenken der Z-Achse bis das Werkzeug die Oberfläche leicht ankratzt und dann das Festlegen der Werkstückkoordinaten.

Image: Skizze Werkzeuglängensensor Sobald das Makro programmiert und eingerichtet ist, wird der WZLS möglichst mittig auf dem Werkstück platziert und die Spindel der Maschine mit ein paar Millimeter Luft direkt über diesem in Position gebracht. Dann wird das Makro gestartet. Die Maschine wird nun automatisch ihre Z-Achse langsam absenken, bis der Sensor schaltet. Dann fährt sie sehr langsam zurück, bis der Sensorkontakt wieder geschlossen ist. Dieser Punkt wird dann zur Bestimmung von Z0 herangezogen, welcher dann automatisch gesetzt wird.

Voraussetzungen

Bevor wir das Makro schreiben können, benötigen wir die Z-Position des Werkzeuglängensensors “zTls” zum Zeitpunkt des Umschaltens von “ausgelöst” auf “nicht ausgelöst”. Ich habe meine vorerst (denn das ist nicht sehr genau) mit einem Messschieber gemessen und notiert. Wir brauchen diesen Wert, damit die Maschine ihn beim Ansetzen von der Z-Höhe abziehen kann, um die Position der Werkstückoberfläche zu ermitteln.

Wie man die Messung des Auslösepunktes präziser hinbekommt soll Gegenstand eines eigenen Artikels werden.

Außerdem ist es wichtig zu wissen, wie der Schalter mit der Maschine verbunden ist. Die bevorzugte Art ist “NC”, d.h. der Schalter öffnet sich, wenn er ausgelöst wird: “Ausgelöst = 0”.

Schließlich müssen wir den Vorwärts- und Rückwärtsvorschub des Messtasters festlegen, z.B. Absenken = 100mm/min, Anheben = 10mm/min.

Das Makro enthält auch einen etwas aufwändigeren Teil: Wenn Z0 gemessen werden soll, obwohl die aktuell Werkzeuglänge noch unbekannt ist, macht das keinen Sinn: Bei einem Werkzeugwechsel wäre sie nicht in der Lage, den Nullabgleich auszuführen.

Daher wird in diesem Fall die Werkzeugvermessung zwischengeschoben: die Maschine speichert die aktuelle Position posX, posY vor der Fahrt zum Werkzeuglängensensor in Hilfsvariablen zwischen und führt direkt das Makro get_tool_length aus. Anschließend fährt sie wieder in die Ausgangsposition zurück, um die Vermessung von Z0 nachzuholen.

Das Unterprogramm “Z0 bestimmen”

Nach dem Ausschalten der Spindel senkt das Programm mit dem Befehl G38.2 die Z-Achse der Maschine ab, bis der Fräser Werkzeuglängensensor auslöst (oder bis sie ohne eingelegtes Werkzeug so weit nach unten gefahren ist, dass die Spindelnase den Sensor fast berührt, wo das Makro dann wiederum abbrechen würde, weil es den Sensor nicht gefunden hat). Sobald der Sensor ausgelöst, fährt die Maschine vorsichtig die Z-Achse wieder hoch. Der Punkt der erneuten Freigabe des Sensors durch das Werkzeug wird dann als Koordinaten-Offset für die Z-Achse mit dem Befehl G92 gespeichert.

Code: Werkstücknullpunkt bestimmen

Bevor wir beginnen stellen wir sicher, dass alle Vorbedingungen erfüllt sind:

  • Die Werkzeuglänge ist bekannt
  • Der WZLS ist korrekt angeschlossen
SUB measure_workpiece_z0
    IF ![toolLengthStat]
        DLGMSG "WARNING - Please first measure tool length!"
        IF [dialogButton == 1]
            xPosReposition = xPos
            yPosReposition = yPos
            repositionTo = 1
            GOSUB get_tool_length
        ENDIF
    ENDIF

    GOSUB is_sensor_ok              ;# Check if tool length sensor status is OK

Programmieren wir nun die eigentliche Routine, indem wir abfragen, ob der Z-Nullabgleich jetzt durchgeführt werden soll. Wenn die CNC im Simulationsmodus läuft (ohne angeschlossene Hardware), wird der Z-Nullabgleich nicht funktionieren. Also müssen wir auch dies überprüfen und den Schritt gegebenenfalls überspringen.

    DLGMSG "Start Z-Zeroing?"
    IF [dialogButton == 1] AND [operatingMode != simulator]
        M5                          ;#Switch spindle off
        M9                          ;#Switch coolant off
        G53 G38.2 Z[zSpindleTip + 5] F[touch] 
                                    ;# G38.2 = touch toward probe, stop on contact, flag error on fail
                                    ;# fail = 5mm before touching spindle tip
        IF [probeOk == 1]           ;# #5067 == 1 : G38.2 command success
            G38.2 G91 Z20 F[rev]    ;# now reverse slowly to find untrigger point (max. 20mm up)
            G90                     ;# back to absolute coordinates
            IF [probeOk == 1]      
                G00 Z[zTouched]     ;# #5063 Go to Z-axis's probe point	
                G92 Z[zTls]         ;# Set Z-axis's coordinate offset (0) to tool length sensor's height
                G00 Z[zTls + 5]     ;# clear sensor
            ELSE
                ERRMSG "Could not locate sensor untrigger point."
            ENDIF 
        ELSE
            DLGMSG "Could not locate sensor trigger point. Retry?" 
            IF [dialogButton == 1]  				
                GOSUB measure_workpiece_z0
            ELSE
                ERRMSG "User abort."
            ENDIF
    ENDIF
ENDSUB

Ausführen des Makros Z-Nullabgleich auf Zerspanobert

Messung der Werkzeuglänge

Wenn die Maschine die Werkzeuglänge kennt, bevor sie den Z-Nullpunkt misst, muss der Z-Nullpunkt bei anstehenden Werkzeugwechseln während des Auftrags nicht erneut bestimmt werden. Das passende Makro beschreibe ich im Folgenden.

Vorbereitung

Image: get_tool_length macro parameters

Die meisten CNCs leiten ihre aktuellen Positionsdaten durch Zählen der Motorschritte ab, die sie nach Abschluss der Referenzfahrt ausgeführt hat. Um nun die Werkzeuglänge zu vermessen, benötigt sie ein paar einprogrammierte Parameter:

  • Z-Position der Spindelnase zSpindleTip (berührt Werkzeuglängensensor ohne eingelegtes Werkzeug) damit berechnet werden kann, wie weit das Werkzeug aus der Spindel herausragt
  • X-Position xPosTls des Werkzeuglängensensors beim Werkzeugwechsel
  • Y-Position yPosTls des Werkzeuglängensensors bei Werkzeugwechseln
  • Sicherheitshöhe zSafety (stellt sicher, dass die Spindel auf ihrem Weg zum Werkzeugwechselbereich nicht auf ein Hindernis stößt)
  • Tastvorschub vorwärts und rückwärts. Wir können ihn aus dem Makro für den Werkstücknullpunkt wiederverwenden.

Die Unterroutine “Werkzeuglänge messen”

Legen wir zunächst fest, was diese Unterroutine tun soll:

  1. Spindel, Kühlmittel etc. anhalten.
  2. Dialog: Bediener soll geschätzte Werkzeuglänge eingeben
  3. Eilgang Z auf Sicherheitshöhe, dann XY zur Position des Werkzeugsensors
  4. Werkzeuglängensensor antasten, ähnlich wie bei der Werkstücknullpunktbestimmung
  5. Werkzeuglänge berechnen
  6. Berechnung der Differenz zwischen der letzten und der aktuellen Werkzeuglänge
  7. Z-Nullpunkt entsprechend aktualisieren
  8. Z im Eilgang wieder auf Sicherheitshöhe bringen

Code: Werkzeug vermessen

SUB get_tool_length
    GOSUB is_sensor_ok                                  ;# Check if tool length sensor status is OK

    DLGMSG "Start tool length measurement? Please enter estimated tool length: " [toolLengthEst]
    IF [dialogButton == 1] AND [operatingMode != simulator]
        IF [toolLengthEst < 0]
            ERRMSG "Error: tool length cannot be negative."
        ENDIF

        IF [zSpindleTip+ toolLengthEst + 10] > [zSafety]
            ERRMSG "Error: tool too long - could collide with sensor."
        ENDIF

        M5                                              ;# Switch spindle off
        M9                                              ;# Switch coolant off
        G53 G00 Z[zSafety]                              ;# Go to safety height (machine coordinates)
        G53 G00 X[xPosTls] Y[xPosTls]                   ;# Go to tool length sensor
        G53 G00 Z[zSpindleTip + toolLengthEst + 10]     ;# Move Z down to 10mm above estimated tool tip

        ;# measure tool length, save results, apply Z-offset if needed
        G53 G38.2 Z[zSpindleTip] F[touch]                   ;# probe sensor, latest stop point is spindle tip
        IF [probeOk == 1]                                   ;# #5067 == 1 : G38.2 command success
            G38.2 G91 Z20 F[rev]                            ;# now reverse slowly to find untrigger point
            G90                                             ;# back to absolute coordinates
            IF [probeOk == 1]  
                toolLength = [zTouched - sTip]
                MSG "Tool length = " toolLength

                IF [toolLengthStat == 1]                    ;# defaults to 0 on startup
                    lastToolLength = currToolLength         ;# save last tool's length
                    currToolLength = toolLength             ;# save current tool's length
                    toolLengthDiff = [currToolLength - lastToolLength]	
                    G92 Z[zPos - toolLengthDiff]            ;# Set Z-axis's coordinate offset (0)
                ELSE
                    currToolLength = toolLength             ;# save current tool's length
                ENDIF
                toolLengthStat = 1
                GOSUB reposition_spindle
            ELSE
                ERRMSG "Could not locate sensor untrigger point."
            ENDIF
        ELSE
            ERRMSG "Could not locate sensor trigger point."
        ENDIF
    ENDIF
ENDSUB   

Werkzeuglängenmakro auf meiner Maschine

In diesem Video ist die Maschine so konfiguriert, dass sie zum XY-Nullpunkt zurückkehrt, wenn die Werkzeugmessung abgeschlossen ist.

Werkzeugwechsel

Wenn es kein Makro für den Werkzeugwechsel gibt, hält die Maschine ihren Auftrag an. Sobald die Z0-Position des Werkstücks erneut ermittelt wurde (denn die Werkzeuglänge hat sich ja wahrscheinlich geändert), kann der Auftrag fortgesetzt werden.

Diese Aufgabe kann automatisiert werden, indem der Z-Nullpunkt durch Bestimmen der neuen Werkzeuglänge gegenüber der alten neu kalibriert wird.

Voraussetzungen

Ein optionales Flag getToolLength kann das Verhalten des Werkzeugwechsel-Makros konfigurieren - ob die Länge jedes Werkzeugs nach einem Werkzeugwechsel gemessen werden soll oder nicht. Wenn Sie einen automatischen Werkzeugwechsler haben, brauchen Sie dies wahrscheinlich nicht: In diesem Falle ist die Werkzeuglänge in einer Tabelle festgehalten und die Längenkompensation erfolgt ohne Vermessung.

Die folgenden zusätzlichen Parameter werden ebenfalls benötigt:

  • X-Position xToolChg wo der Werkzeugwechsel stattfindet (bei mir ist es xToolChg = xPosTls)
  • Y-Position yToolChg, wo der Werkzeugwechsel stattfindet (bei mir ist es yToolChg = yPosTls)
  • Wiederverwendung von zSafety aus get_tool_length Makro
  • newToolNumber zur Angabe des angeforderten Werkzeugs aus dem G-Code (oder bei manueller Eingabe)
  • currToolNumber zur Angabe des “alten” Werkzeugs, das ersetzt werden soll
  • ToolChangeDone-Hilfsflag, um anzuzeigen, ob ein Werkzeugwechsel bereits stattgefunden hat

Das Makro “Werkzeugwechsel”

Die folgenden Schritte werden von dem Makro ausgeführt, wenn ein Werkzeugwechsel entweder durch den Befehl M06 “Werkzeugwechsel” in der G-Code-Datei eines Jobs oder manuell durch Benutzer ausgelöst wird:

  1. Spindel, Kühlmittel etc. anhalten.
  2. Wenn das angeforderte Werkzeug das aktuelle Werkzeug ist, erscheint ein Dialog mit der Frage, ob es trotzdem gewechselt werden soll.
  3. Z im Eilgang nach oben (zSafety), dann XY auf die Werkzeugwechselposition fahren
  4. Dialog: Angabe des aktuellen Werkzeugs a und Aufforderung, das gewünschte Werkzeug b einzusetzen
  5. Prüfen, ob die Werkzeugwechselkonfiguration die Bestimmung der Werkzeuglänge impliziert. Wenn ja, Makro “get_tool_length” aufrufen.

Code

SUB change_tool
    toolChangeDone = 0
    M5                                              ;# Switch spindle off
    M9                                              ;# Switch coolant off

    IF [operatingMode != simulator]
        TCAGuard off                              ;# tool change area guard: off for tool change

        ;# handle case that tool is already in place
        IF [newToolNumber == currToolNumber]
            DLGMSG "Tool already mounted. Change anyways?"
            IF [dialogButton == 1]
                toolChangeDone = 0
            ELSE
                toolChangeDone = 1
            ENDIF
        ENDIF

        ;# go to tool change position and prompt to change tool
        IF [toolChangeDone == 0]
            G53 G00 Z[zSafety]                        ;# Go to safety height (machine coordinates)
            G53 G00 X[xToolChg] Y[yToolChg]           ;# Go to tool change position
            DLGMSG "Please mount tool now. Old tool number: " [currToolNumber] 
                    " New tool number: " [newToolNumber]
            IF [dialogButton == 1]
                IF [newToolNumber > 99] OR [newToolNumber < 0]
                    TCAGuard on                       ;# tool change area guard: on for normal job
                    ERRMSG "New tool number implausible."
                ENDIF
                toolChangeDone = 1
            ELSE
                ERRMSG "Tool change aborted."
            ENDIF
        ENDIF

        ;# prompt when complete and optionally call tool length measurement
        IF [toolChangeDone == 1]   
            MSG "Tool change from " [currToolNumber] " to "  [newToolNumber] " complete."
            M6 T[newToolNumber]				      ;# set new tool number

            IF [getToolLength == 1]               ;# config flag 0 = no, 1 = yes
                GOSUB get_tool_length             ;# Measure tool length. Careful: To be called after M6 T !
            ELSE
                GOSUB reposition_spindle
            ENDIF
        ENDIF

        TCAGuard on                               ;# tool change area guard: on for normal job
    ENDIF
ENDSUB

Repositionieren (auf gespeicherte Koordinaten)

Dieses kurze Makro wird von anderen Unterprogrammen aufgerufen, um die Maschine entweder zu einer gewünschten Position oder zum XY-Werkstücknullpunkt zu fahren. Es kann mit mehreren repositionTo-Flag-Werten erweitert werden, z.B.

0 = keine_Neupositionierung, 1 = benutzerdefinierte_Position, 2 = Werkstücknullpunkt, 3 = Maschinennullpunkt ...

Code

SUB reposition_spindle
    G53 G00 Z[zSafety]                          ;# Go to safety height (machine coordinates)
    IF [repositionTo == 1]
        G00 X[xPosReposition] Y[yPosReposition] ;# Move back to where requested before
        repositionTo = 0                        ;# reset reposition flag and values
        xPosReposition = 0
        yPosReposition = 0
    ELSE
        G00 X0 Y0                               ;# Move back to XY zero (workpiece coordinates)
    ENDIF
ENDSUB

Tabelle der Variablen

Typ-Erläuterungen:

  • protected: Parameter gehört zu festen Befehlen oder Zuständen, die schreibgeschützt sind
  • reserved : Parameter hat eine feste Verwendung innerhalb meiner CNC-Software
  • free : Parameter kann frei durch den Programmierer verwendet werden (Ist nach Not-Aus oder Neustart aber zurückgesetzt!)
  • persisted: Parameter wird im nicht-flüchtigen Speicher abgelegt

Systemparameter

Variable name Parameter nr. Type Comment
xPos #5001 protected current CNC position
yPos #5002 protected current CNC position
zPos #5003 protected current CNC position
currToolNumber #5008 protected [1…99]
newToolNumber #5011 protected [1…99]
zTouched #5063 protected Z where sensor touched
probeOk #5067 protected 1 = OK
sensorStatus #5068 protected 1 = triggered
operatingMode #5397 reserved 1 = simulator
dialogButton #5398 reserved 1 = OK

Konfig-Parameter

Config-Parameter sind die Parameter, die einmal eingestellt und dann konstant gehalten werden, da sie an die CNC und ihre Geometrie gebunden sind.

Die Volatilität der Parameter hängt stark von der CNC-Softwarelösung ab, die Sie verwenden, und die Anzahl der freien Parameter kann unterschiedlich sein. In meiner Software werden die Variablen im Bereich “#4000 - #4999” beibehalten, während alle anderen freien und reservierten Parameter flüchtig und/oder skaliert sind.

Variable name Parameter nr. Type Comment
triggered #4400 free, persisted 1 = normally open
currToolLength #4501 free, persisted [mm]
lastToolLength #4502 free, persisted [mm]
zSafety #4506 free, persisted Z position for move
xPosTls #4507 free, persisted tls position
xPosTls #4508 free, persisted tls position
zSpindleTip #4509 free, persisted Z zpindle on tls
zTls #4510 free, persisted TLS height [mm]
toolLengthEst #4511 free, persisted [mm]
touch #4512 free, persisted touch feed [mm/min]
rev #4513 free, persisted reverse feed [mm/min]
getToolLength #4520 free, persisted Flag, 1 = Yes
xToolChg #4521 free, persisted tool change position
yToolChg #4522 free, persisted tool change position

Flag-Parameter

Freie Parameter können nach Belieben verwendet werden. Es muss jedoch darauf geachtet werden, dass nicht versehentlich eine bestehende Systemvariable wiederverwendet oder überschrieben wird.

Variable name Parameter nr. Type Comment
toolLengthStat #3501 free 1 = measured
toolLengthDiff #3502 free [mm]
toolChangeDone #5015 free 1 = Yes
repositionTo #5020 free Flag, 1 = Yes
xPosReposition #5021 free reposition flag
yPosReposition #5022 free reposition flag
toolLength #5024 free [mm]

PS: Damit die obigen Code-Beispiele ordentlich angezeigt werden, habe ich fürs Markup die Sprachinterpretation ruby verwendet. Hier kommt die farbige Syntax-Markierung am besten überein mit der Logik des NGC-interpreters.