Vismach is a set of Python functions that can be used to create and animate models of machines.

In diesem Kapitel geht es um die in Qt eingebettete Version von Vismach, siehe auch: https://sa-cnc.com/linuxcnc-vismach/ .

1. Einführung

Vismach zeigt das Modell in einem 3D-Ansichtsfenster (engl. viewport) an und die Modellteile werden animiert, wenn sich die Werte der zugehörigen HAL-Pins ändern.

QtVismach 3D Ansicht
Abbildung 1. QtVismach 3D Ansicht

Das Vismach 3D Ansichtsfenster (engl. viewport view) kann wie folgt manipuliert werden:

  • zoom by scroll wheel

  • pan by middle button drag

  • rotate by right-button drag

  • tilt by left button drag

Ein Vismach-Modell hat die Form eines Python-Skripts und kann die Standard-Python-Syntax verwenden.

Das bedeutet, dass es mehr als eine Möglichkeit gibt, das Skript zu gestalten, aber in den Beispielen in diesem Dokument wird die einfachste und grundlegendste davon verwendet.

Die grundlegende Reihenfolge bei der Erstellung des Vismach-Modells ist:

  1. Erstellen der Teile

  2. Definieren, wie sie sich bewegen

  3. In Bewegungsgruppen zusammenstellen

2. Hierarchie des Maschinendesigns

Das Modell folgt einer logischen Baumstruktur.

Stellen Sie sich einen Baum vor, mit Wurzel/Stamm, Ästen und kleineren Zweigen. Wenn Sie den größeren Ast bewegen, dann bewegen sich die kleineren Äste mit, aber wenn Sie den kleineren Ast bewegen, so bewegen sich die größeren dennoch nicht.

Das Maschinendesign folgt diesem konzeptionellen Design.

Nehmen wir als Beispiel die Fräse, die im obigen Bild des 3D-Ansichtsfensters zu sehen ist:

  • Wenn Sie X bewegen, kann sie sich von selbst bewegen,

  • aber wenn Sie die Baugruppe Y verschieben, wird auch die Baugruppe X verschoben, da sie mit der Baugruppe Y verbunden ist.

Für diese Maschine sieht der Baum so aus:

model
  |
  |---frame
  |     |
  |     |---base
  |     |
  |     |---column
  |     |
  |     |---top
  |
  |---yassembly
  |      |
  |      |---xassembly
  |      |      |
  |      |      |---xbase
  |      |      |
  |      |      |---work
  |      |
  |      |---ybase
  |
  |---zassembly
          |
          |---zframe
          |     |
          |     |---zbody
          |     |
          |     |---spindle
          |
          |---toolassembly
                    |
                    |---cat30
                    |
                    |---tool
                         |
                         |---tooltip
                         |
                         |---(tool cylinder function)

Wie Sie sehen, muss das niedrigste Teil zuerst existieren, bevor es mit anderen zu einer Baugruppe zusammengefasst werden kann. Sie bauen also vom niedrigsten Punkt im Baum aufwärts und fügen sie zusammen.

Dasselbe gilt für jede Art von Maschinenkonstruktion: Schauen Sie sich das Beispiel des Maschinenarms an, und Sie werden sehen, dass er mit der Spitze beginnt und sich zum größeren Teil des Arms hinzugesellt, um sich schließlich mit der Basis zu vereinen.

3. Starten des Skripts

Zum Testen ist es nützlich, die Shebang-Zeile #!/usr/bin/env python3 einzufügen, damit die Datei direkt von der Kommandozeile ausgeführt werden kann.

Zunächst sind die erforderlichen Bibliotheken zu importieren.

#!/usr/bin/env python3

import hal
import math
import sys

from qtvcp.lib.qt_vismach.qt_vismach import *

4. HAL-Pins.

Ursprünglich erforderte die Vismach-Bibliothek die Erstellung einer Komponente und den Anschluss von HAL-Pins zur Steuerung der Simulation.

qt_vismach kann die HAL-Systempins direkt lesen oder, wenn Sie es wünschen, separate HAL-Pins verwenden, die Sie in einer HAL-Komponente definieren müssen:

c = hal.component("samplegui")
c.newpin("joint0", hal.HAL_FLOAT, hal.HAL_IN)
c.newpin("joint1", hal.HAL_FLOAT, hal.HAL_IN)
c.ready()

You can select between the two options in the functions that take these entries:

hal_comp

The HAL component Object or None.
In QtVCP if you are reading system pins directly, then the component argument is set to None.

hal_pin

The name of the BIT HAL IN pin that will change the color.
if hal_comp is None then this must be the full name of a system pin otherwise this is the pin name excluding the component name

5. Erstellen von Teilen

5.1. Importieren von STL- oder OBJ-Dateien

Das ist wahrscheinlich am einfachsten:

  • Herstellung einer Geometrie in einem CAD-Paket_

  • _Import in das Modellskript unter Verwendung der Funktionen "ASCIISTL()" oder "ASCIIOBJ()".

Beide Funktionen können eines von zwei benannten Argumenten annehmen, entweder einen Dateinamen oder Daten:

part = AsciiSTL(filename="path/to/file.stl")
part = AsciiSTL(data="solid part1 facet normal ...")
part = AsciiOBJ(filename="path/to/file.obj")
part = AsciiOBJ(data="v 0.123 0.234 0.345 1.0 ...")
  • STL-Modellteile werden dem Vismach-Raum an denselben Stellen hinzugefügt, an denen sie im STL- oder OBJ-Raum erstellt wurden, d. h. idealerweise mit einem Rotationspunkt an ihrem Ursprung.

Anmerkung
Es ist viel einfacher, sich während des Bauens zu bewegen, wenn der Ursprung des Modells an einem Drehpunkt liegt.

5.2. Aufbau aus geometrischen Primitiven

Alternativ können Teile auch im Modellskript aus einer Reihe von Formprimitiven erstellt werden.

assembly = collction([part1,part2,part3])

Collection is a general container of related parts

Viele Formen werden am Ursprung erstellt und müssen nach der Erstellung an den gewünschten Ort verschoben werden.

cylinder = CylinderX(x1, r1, x2, r2)
cylinder = CylinderY(y1, r1, y2, r2)
cylinder = CylinderZ(z1, r1, z2, r2)

Erzeugt einen (optional verjüngten) Zylinder auf der gegebenen Achse mit den gegebenen Radien an den gegebenen Punkten auf der Achse.

sphere = Sphere(x, y, z, r)

Erzeugt eine Kugel mit Radius r bei (x,y,z).

triangle = TriangleXY(x1, y1, x2, y2, x3, y3, z1, z2)
triangle = TriangleXZ(x1, z1, x2, z2, x3, z3, y1, y2)
triangle = TriangleYZ(y1, z1, y2, z2, y3, z3, x1, x2)

Erzeugt eine dreieckige Platte zwischen Ebenen, die durch die letzten beiden Werte parallel zur angegebenen Ebene definiert ist und deren Eckpunkte durch die drei Koordinatenpaare gegeben sind.

arc = ArcX(x1, x2, r1, r2, a1, a2)

Erstellen Sie eine Bogenform.

box = Box(x1, y1, z1, x2, y2, z2)

Erzeugt ein rechteckiges Prisma mit gegenüberliegenden Ecken an den angegebenen Positionen und Kanten parallel zu den XYZ-Achsen.

box = BoxCentered(xw, yw, zw)

Erzeugt eine xw mal yw mal zw Box, die auf den Ursprung zentriert ist.

box = BoxCenteredXY(xw, yw, z)

Erstellt einen Kastenboden auf der WY-Ebene mit der Breite xw / yw und der Höhe z.

Zusammengesetzte Teile können durch Zusammenfügen dieser Primitive entweder zum Zeitpunkt der Erstellung oder später erstellt werden:

part1 = Collection([Sphere(100,100,100,50), CylinderX(100,40,150,30)])
part2 = Box(50,40,75,100,75,100)
part3 = Collection([part2, TriangleXY(10,10,20,10,15,20,100,101)])
part4 = Collection([part1, part2])

6. Bewegliche Teile des Modells

Möglicherweise müssen Teile im Vismach-Raum verschoben werden, um das Modell zusammenzusetzen. Der Ursprung bewegt sich nicht - Translate() und Rotate() verschieben die Collection, während Sie Teile hinzufügen, relativ zu einem stationären Ursprung.

6.1. Verschieben von Teilen des Modells

part1 = Translate([part1], x, y, z)

Verschiebe Teil1 um die angegebenen Abstände in x, y und z.

6.2. Rotation von Teilen des Modells

part1 = Rotate([part1], theta, x, y, z)

Drehen Sie das Teil um den Winkel theta [Grad] um eine Achse zwischen dem Ursprung und x, y, z.

7. Animieren von Teilen

Zur Animation des Modells durch die Werte der HAL-Pins gibt es vier Funktionen HalTranslate, HalRotate, HalToolCylinder und HalToolTriangle.

Damit sich Teile innerhalb einer Baugruppe bewegen können, müssen ihre HAL-Bewegungen definiert werden, bevor sie mit dem Befehl "Collection" zusammengebaut werden.

Die Rotationsachse und der Translationsvektor bewegen sich mit dem Teil:

  • wie es vom Vismach-Skript während der Modellmontage verschoben wird, oder

  • während es sich als Reaktion auf die HAL-Pins bewegt, während das Modell animiert wird.

7.1. HalTranslate

part = HalTranslate([part], hal_comp, hal_pin, xs, ys, zs)
part

Eine Sammlung oder ein Teil.
Sie kann zu einem früheren Zeitpunkt im Skript erstellt werden oder, falls gewünscht, an dieser Stelle, z. B.

`part1 = HalTranslate([Box(....)], ...)`. +
hal_comp

Die HAL Komponente ist das nächste Argument.
In QtVCP, wenn Sie System-Pins direkt lesen, wird das Komponentenargument auf None gesetzt.

hal_pin

Der Name des HAL-Pins, der die Bewegung animieren soll.
Dieser muss mit einem bestehenden HAL-Pin übereinstimmen, der die Gelenkposition beschreibt, wie z. B.:

"joint.2.pos-fb"

Andernfalls würde die Komponenteninstanz und der Pin-Name dieser Komponente angegeben werden. xs, ys, zs;; Die X, Y, Z Skalen.
Bei einer kartesischen Maschine, die im Maßstab 1:1 erstellt wurde, wäre dies normalerweise "1,0,0" für eine Bewegung in positiver X-Richtung.
Wenn die STL-Datei jedoch in cm und die Maschine in Zoll erstellt wurde, kann dies an dieser Stelle durch die Verwendung von 0,3937 ( = 1 cm/1 Zoll = 1 cm /2,54 cm ) als Maßstab festgelegt werden.

7.2. HalRotate

part = HalRotate([part], hal_comp, hal_pin, angle_scale, x, y, z)

Dieser Befehl funktioniert ähnlich wie HalTranslate, mit dem Unterschied, dass es normalerweise notwendig ist, das Teil zuerst zum Ursprung zu bewegen, um die Achse zu definieren.

x, y, z

Definiert die Drehachse vom Ursprung, dem Koordinatenpunkt (x,y,z).
Wenn das Teil vom Ursprung zurück an seine richtige Position bewegt wird, kann die Drehachse als im Teil "eingebettet" betrachtet werden.

angle_scale

Drehwinkel werden in Grad angegeben. Für ein Drehgelenk mit einer Skalierung von 0-1 müssten Sie also eine Winkelskala von 360 verwenden.

7.3. HalToolCylinder

tool = HalToolCylinder()

Stellen Sie einen Zylinder her, der ein zylindrisches Fräswerkzeug darstellt, basierend auf dem Werkzeugtisch und dem aktuell belasteten Werkzeug.

tool = HalToolCylinder()
toolshape = Color([1, .5, .5, .5],[tool])

# oder kompakter:
toolshape = Color([1, .5, .5, .5], [HalToolCylinder()])

7.4. HalToolTriangle

tool = HalToolTriangle()

Erstellen Sie ein Dreieck, um ein dreieckiges Drehwerkzeug darzustellen, basierend auf dem Werkzeugtisch und dem aktuell geladenen Werkzeug.

tool = HalToolTriangle()
toolshape = Color([1, 1, 0, 1],[tool])

# oder kompakter:
toolshape = Color([1, 1, 0, 1],[HalToolTriangle()])

7.5. HAL Adjustable Primitives

All shape primitives can have HAL pin names substituted for coordinates.
Either by adding the component object as the first variable and substituting the pinname string for a coordinate, or
by substituting the full component/pinname string for a coordinate.

This example creates a rectangular prism with opposite corners at the specified positions and edges parallel to the XYZ axes.
the Z start coordinate will be controlled by the HAL pin Zstart

box = Box(component, x1, y1, 'Zstart', x2, y2, z2)
box = Box(x1, y1, 'componentName.Zstart', x2, y2, z2)

8. Zusammenbau des Modells

Damit sich die Teile gemeinsam bewegen können, müssen sie mit dem Befehl Collection() zusammengefügt werden.

Es ist wichtig, die Teile zusammenzufügen und ihre Bewegungen in der richtigen Reihenfolge zu definieren.

Um beispielsweise eine Fräsmaschine mit beweglichem Kopf, einer rotierenden Spindel und einer animierten Zugstange zu erstellen, würden Sie dies tun:

  • Erstellen Sie den Hauptteil des Kopfes.

  • Erstellen Sie die Spindel im Ursprung.

  • Definieren Sie die Drehung.

  • Bewegen Sie den Kopf zur Spindel oder die Spindel zum Kopf.

  • Erstellen Sie die Zugstange (engl. draw bar).

  • Definieren Sie die Bewegung der Zugstange.

  • Bauen Sie die drei Teile zu einer Kopfeinheit zusammen.

  • Definieren Sie die Bewegung der Kopfeinheit.

In diesem Beispiel wird die Spindeldrehung durch die Drehung eines Satzes von Mitnehmern angezeigt:

#Drive dogs
dogs = Box(-6,-3,94,6,3,100)
dogs = Color([1,1,1,1],[dogs])
dogs = HalRotate([dogs],c,"spindle",360,0,0,1)
dogs = Translate([dogs],-1,49,0)

#Drawbar
draw = CylinderZ(120,3,125,3)
draw = Color([1,0,.5,1],[draw])
draw = Translate([draw],-1,49,0)
draw = HalTranslate([draw],c,"drawbar",0,0,1)

# head/spindle
head = AsciiSTL(filename="./head.stl")
head = Color([0.3,0.3,0.3,1],[head])
head = Translate([head],0,0,4)
head = Collection([head, tool, dogs, draw])
head = HalTranslate([head],c,"Z",0,0,0.1)

# base
base = AsciiSTL(filename="./base.stl")
base = Color([0.5,0.5,0.5,1],[base])
# mount head on it
base = Collection([head, base])

Schließlich muss eine einzige Sammlung aller Maschinenteile, Böden und Arbeiten (falls vorhanden) erstellt werden.

Für eine serial machine wird jedes neue Teil der Sammlung des vorherigen Teils hinzugefügt.

Bei einer Parallelmaschine kann es mehrere "Basis"-Teile geben.

So wird zum Beispiel in scaragui.py link3 zu link2, link2 zu link1 und link1 zu link0 hinzugefügt, so dass das endgültige Modell wie folgt erstellt wird:

model = Collection([link0, floor, table])

Ein VMC-Modell mit separaten Teilen, die sich auf dem Sockel bewegen, könnte hingegen haben

model = Collection([base, saddle, head, carousel])

9. Weitere Funktionen

9.1. Farbe

Sets the display color of the part.

part = Color([_colorspec_], [_part_])

Note that unlike the other functions, the part definition comes second in this case.

_colorspec_

Three (0-1.0) RGB values and opacity. [R,G,B,A]
For example [1.0,0,0,0.5] for a 50% opacity red.

9.2. HALColorFlip

Sets the display color of the part based on a designated HAL bit pin state.

part = HALColorFlip([_colorspec_], [_colorspec_], [_part_], hal_comp, hal_pin)

Note that unlike the other functions, the part definition comes second in this case.

_colorspec_

Three (0-1.0) RGB values and opacity.
For example [1.0,0,0,0.5] for a 50% opacity red.

hal_comp

The HAL component Object or None.
In QtVCP if you are reading system pins directly, then the component argument is set to None.

hal_pin

The name of the BIT HAL IN pin that will change the color.
if hal_comp is None then this must be the full name of a system pin other wise this is the pin name excluding the component name

9.3. HALColorRGB

Sets the display color of the part based on a designated HAL U32 pin value.
The color is decoded from the U32 value. each color is a 0-255 decimal value (shown here in hex)
red = 0xXXXXXXRR
green = 0xXXXXGGXX
blue = 0xXXBBXXXX
combined as 0xXXBBGGRR

part = HALColorRGB([_part_], hal_comp, hal_pin, alpha=1.0)
hal_comp

The HAL component Object or None.
In QtVCP if you are reading system pins directly, then the component argument is set to None.

hal_pin

The name of the U32 HAL IN pin that will change the color.
if hal_comp is None then this must be the full name of a system pin other wise this is the pin name excluding the component name

alpha=

Sets the opacity. (0-1.0)

9.4. Heads Up Display

Erzeugt ein Heads-up-Display in der Vismach-GUI, um Elemente wie Achsenpositionen, Titel oder Meldungen anzuzeigen.

myhud = Hud()
myhud = Hud()
myhud.show("Mill_XYZ")`

9.5. HAL Heads Up Display

Eine erweiterte Version des Hud, mit der HAL-Pins angezeigt werden können:

myhud = HalHud()
myhud = HalHud()
myhud.display_on_right()
myhud.set_background_color(0,.1,.2,0)
myHud.set_text_color(1,1,1)
myhud.show_top("Mill_XYZ")
myhud.show_top("------------")
myhud.add_pin('axis-x: ',"{:10.4f}","axis.x.pos-cmd")
myhud.add_pin('axis-y: ',"{:10.4f}","axis.y.pos-cmd")
myhud.add_pin('axis-z: ',"{:10.4f}","axis.z.pos-cmd")
myhud.show("-------------")

Some of the available HalHUD function:

  • set_background_color(red, green, blue, alpha)

  • add_pin(text, format, pinname)

  • set_text_color(red, green, blue)

9.6. HideCollection

 HideCollection is a container that uses a HAL pin to control display of the contained parts. +
A logic high on the HAL pin will hide the contained parts.
comp.newpin("hide-chuck", hal.HAL_BIT, hal.HAL_IN)
# <snip make a machine with an A axis chuck assembly>
chuckassembly = HideCollection([chuckassembly],comp,'hide-chuck')
# also can be used like this
chuckassembly = HideCollection([chuckassembly],None,'myvismach.hide-chuck')

9.7. Plot Color Based on Mtotion Type

If you wish to plot different colors for different motions you need to add some more python code.

add this at the top of the file:

from qtvcp.core import Status
STATUS = Status()

and this to the Window class:

        STATUS.connect('motion-type-changed', lambda w, data: v.choosePlotColor(data))

        # uncomment to change feed color and to see all colors printed to the terminal
        #v.setColorsAttribute('FEED',(0,1,0))
        #print(v.colors)

You can set DEFAULT, FEED, TRAVERSE, ARC, PROBE, ROTARYINDEX, TOOLCHANGE colors with setColorsAttribute()

9.8. Capture

Damit wird die aktuelle Position im Modell festgelegt.

part = Capture()

9.9. main

This is the command that makes it all happen, creates the display, etc. if invoked directly from Python.
Usually this file is imported by QtVCP and the window() object is instantiated and embedded into another screen.

main(model, tooltip, work, size=10, hud=myhud, rotation_vectors=None, lat=0, lon=0)
_model_

Sollte eine Sammlung sein, die alle Maschinenteile enthält.

tooltip und work

Müssen durch Capture() erstellt werden, um ihre Bewegung im Backplot zu visualisieren. Siehe mill_xyz.py für ein Beispiel, wie man die Werkzeugspitze mit einem Werkzeug und das Werkzeug mit dem Modell verbindet.

_size_

Sets the extent of the volume visualized in the initial view.

_hud_

refers to a head-up display.

"rotation_vectors" oder "lat, lon"

Kann verwendet werden, um den ursprünglichen Blickwinkel festzulegen. Es ist ratsam, zu tun, wie die Standard-Anfangsstandpunkt ist eher wenig hilfreich von unmittelbar über Kopf.

10. Hinweise

Erstellen Sie für Konstruktionszwecke eine Achsenursprungsmarkierung, um Teile relativ dazu sehen zu können. Sie können diese entfernen, wenn Sie fertig sind.

# Erstellen von Achsen-Ursprungs-Markierungen
X = CylinderX(-500,1,500,1)
X = Color([1, 0, 0, 1], [X])
Y = CylinderY(-500,1,500,1)
Y = Color([0, 1, 0, 1], [Y])
Z = CylinderZ(-500,1,500,1)
Z = Color([0, 0, 1, 1], [Z])
origin = Collection([X,Y,Z])

Fügen Sie es der Window-Klassensammlung hinzu, damit es nie vom Ursprung verschoben wird.

v.model = Collection([origin, model, world])

Beginnen Sie an der Schneidspitze (engl. cutting tip) und arbeiten Sie sich zurück. Fügen Sie jede Auflistung dem Modell am Ursprung hinzu, und führen Sie das Skript aus, um den Speicherort zu bestätigen. Drehen / Übersetzen und Ausführen des Skripts zur erneuten Bestätigung.

11. Grundstruktur eines QtVismach-Skripts

# imports
import hal
from qtvcp.lib.qt_vismach.qt_vismach import *

# import Status for motion type messages
from qtvcp.core import Status
STATUS = Status()

# hier HAL-Pins erstellen, falls erforderlich
#c = hal.component("samplegui")
#c.newpin("joint0", hal.HAL_FLOAT, hal.HAL_IN)

# Erstellen des Bodens, des Werkzeugs und des Werkstücks
floor = Box(-50, -50, -3, 50, 50, 0)
work = Capture()
tooltip = Capture()

# Das Modell aufbauen und zusammensetzen
part1 = Collection([Box(-6,-3,94,6,3,100)])
part1 = Color([1,1,1,1],[part1])
part1 = HalRotate([part1],None,"joint.0.pos-fb",360,0,0,1)
part1 = Translate([dogs],-1,49,0)

# ein Top-Level-Modell erstellen
model = Collection([base, saddle, head, carousel])

# wir wollen entweder in QtVCP einbetten oder direkt mit PyQt5 anzeigen
# also ein Fenster bauen, um das Modell anzuzeigen

class Window(QWidget):

    def __init__(self):
        super(Window, self).__init__()
        self.glWidget = GLWidget()
        v = self.glWidget
        v.set_latitudelimits(-180, 180)

        world = Capture()

        # unkommentiert lassen, wenn es ein HUD gibt
        # HUD muss wissen, wo es zeichnen soll
        #v.hud = myhud
        #v.hud.app = v

        # update plot color based on motion type
        STATUS.connect('motion-type-changed', lambda w, data: v.choosePlotColor(data))

        # uncomment to change feed color
        #v.setColorsAttribute('FEED',(0,1,0))
        #  and to see all colors printed to the terminal
        #print(v.colors)

        v.model = Collection([model, world])
        size = 600
        v.distance = size * 3
        v.near = size * 0.01
        v.far = size * 10.0
        v.tool2view = tooltip
        v.world2view = world
        v.work2view = work

        mainLayout = QHBoxLayout()
        mainLayout.addWidget(self.glWidget)
        self.setLayout(mainLayout)

# Wenn Sie diese Datei direkt aus Python3 aufrufen, wird ein PyQt5-Fenster angezeigt.
# das sich gut eignet, um die Teile der Baugruppe zu bestätigen.

if __name__ == '__main__':
    main(model, tooltip, work, size=600, hud=None, lat=-75, lon=215)

12. Integrierte Vismach-Beispielpanels