OUT := FUNKTION(Signal, Sekunden)
TON(IN, 2.5) â EinschaltverzĂśgert: Schaltet erst ein, wenn IN fĂźr 2.5s anliegt.TOF(IN, 1.5) â AusschaltverzĂśgert: Bleibt nach Abfall von IN noch 1.5s aktiv.TP(IN, 3.0) â Impuls: Ein kurzer IN-Puls erzeugt ein festes 3.0s Signal.OUT := CTU(Zähl_Signal, Reset, Limit) â Vorwärtszähler: Zählt bei jedem Signalwechsel (0 auf 1). Wenn das Limit erreicht ist, schaltet OUT ein.
A, B
Oder als Raw-Code kopieren / hier einfĂźgen:
đ Anlage als direkten Link teilen:
Kopiere diesen Text, fĂźge ganz unten deine Aufgabe ein und gib ihn einer KI (ChatGPT, Claude, Gemini). FĂźge die JSON-Antwort dann in das VALIS Raw-Code Feld ein!
({
// ââ Pflichtfelder ââââââââââââââââââââââââââââââââââââââââââââââ
meta: { name: 'Mein Plugin', icon: 'đľ', desc: 'Kurzbeschreibung' },
menuTarget: '#menu-aktoren', // Ziel-MenĂź (siehe unten)
// â ď¸ WICHTIG: Der SchlĂźssel in addFactoryDevice() MUSS dem Dateinamen entsprechen!
// Dateiname: mein_plugin.js â SchlĂźssel: 'mein_plugin'
menuHtml: '<div onclick="addFactoryDevice(\'mein_plugin\')">đľ Mein Plugin</div>',
build: function(obj, el, var1, var2, var3, color, interval) { /* ... */ },
// ââ Optionale Felder âââââââââââââââââââââââââââââââââââââââââââ
i18n: { de: { name: '...', desc: '...' }, en: { name: '...', desc: '...' } },
modalConfig: { t: 'Titel:', l1: 'Variable:', l2: 'Var2:', l3: 'Var3:', out: true, customList: 'timers' },
css: '.meine-klasse { color: red; }', // CSS wird in <head> eingefĂźgt
role: 'output', // NUR 'output' (Aktor) oder 'input' (Sensor) â steuert KontextmenĂź!
color: '#4caf50', // Standard-Farbe des Objekts
noRodAttach: true, // true â Objekt kann NICHT an Zylinder-Kolben montiert werden
canMount: true, // true â andere Objekte kĂśnnen an dieses Plugin montiert werden
noContainerStyle: true, // true â entfernt f2d-obj Box-Shadow/Hintergrund vom Container
// nĂśtig wenn das Plugin eigene Kind-Divs fĂźr seine Grafik nutzt
directSpawn: true, // true â kein Modal, sofort platzieren
hasHeight: true, // true â Objekt hat einstellbare HĂśhe (z.B. Regal)
isFlatBelt: true, // true â behandelt als flaches FĂśrderband
clickSimulate: true, // true â Klick im Simulationsmodus toggelt linkedVar
isNC: true, // true â Ăffner-Logik (normally closed)
noRotate: true, // true â Objekt kann nicht rotiert werden
sensorSteps: 4, // Anzahl Abtastpunkte entlang des Sensors (Standard: 1)
animate: function(obj, spsState) { /* ... */ },
physics: function(obj, item, isRunning, rad) { /* ... */ },
contextMenu: function(obj, ctxMenu, saveCallback) { /* ... */ },
customSensorHit: function(targetObj, sensorObj, lx, ly, lw, lh) { /* ... */ },
})
menuTarget: '#menu-aktoren' // Aktoren (Lampen, Motoren, Zylinder âŚ) menuTarget: '#menu-sensoren' // Sensoren (Lichtschranken, Taster âŚ) menuTarget: '#menu-bedienpult' // Bedienpanel (Taster, Schalter, Ampeln âŚ)
role: 'output' // Aktor: liest SPS-Ausgangsvariablen (z.B. Motor, Lampe, Zylinder)
// â Rechtsklick-KontextmenĂź zeigt "Ausgangsvariable ändern"-Dropdown
role: 'input' // Sensor: schreibt SPS-Eingangsvariablen (z.B. Taster, Lichtschranke)
// â Rechtsklick-KontextmenĂź zeigt "Eingangsvariable ändern"-Dropdown
// â ď¸ Achtung: Nur 'output' und 'input' sind gĂźltige Werte!
// 'actuator', 'sensor' o.ä. werden NICHT erkannt und fßhren dazu, dass im
// KontextmenĂź kein Variablen-Dropdown erscheint.
modalConfig: {
t: 'Beschriftung:', // Ăberschrift des Modals (Pflicht)
l1: 'Variable 1', // Label fĂźr das erste Dropdown (optional, Alias fĂźr t)
l2: 'Variable 2', // Label fĂźr zweites Dropdown (optional, z.B. Einfahren)
l3: 'Variable 3', // Label fĂźr drittes Dropdown (optional, z.B. Greifer)
// ââ out: bestimmt welche SPS-Variablenliste im Dropdown erscheint âââââââââ
out: true, // Dropdown zeigt SPS-AUSGANGSVARIABLEN (linke Seite von ':=' im Code)
// Verwenden fĂźr: Aktoren, Motoren, Lampen, Zylinder âŚ
// Code-Injektion beim Anlegen: 'VARNAME := FALSE'
out: false, // Dropdown zeigt SPS-EINGANGSVARIABLEN (per 'INPUT' deklariert)
// (fehlt) // Verwenden fĂźr: Sensoren, Taster, Lichtschranken âŚ
// Code-Injektion beim Anlegen: 'INPUT VARNAME'
// â ď¸ 'out' und 'role' mĂźssen zusammenpassen:
// Aktor â out: true, role: 'output'
// Sensor â out: false, role: 'input'
customList: 'timers', // Dropdown zeigt stattdessen Timer/Zähler-Variablen aus dem Logikplan
// (TON, TOF, TP, CTU) â l2/l3 werden dann ignoriert
}
// Wird beim Platzieren/Laden eines Objekts aufgerufen.
// obj â das Factory-Objekt (persistenter Zustand, wird gespeichert)
// el â das DOM-Element in der 2D-Anlage
// var1..3 â Variablennamen aus dem Modal (strings)
// color â Hex-Farbe (z.B. '#4caf50') â bereits aufgelĂśst, KEIN Farbname!
// interval â Takt-Intervall (Sekunden, z.B. bei Timern)
//
// â ď¸ el.style.position NICHT setzen â VALIS verwaltet die Positionierung selbst!
// â
el.style.width/height NICHT nĂśtig â VALIS setzt beides automatisch nach build()!
// (VALIS liest obj.width/height und ßberträgt diese auf el.style.width/height)
// â ď¸ DOM-Referenzen auf Kind-Elemente in build() cachen, NICHT in animate() suchen!
build: function(obj, el, var1, var2, var3, color, interval) {
// ââ Pflicht: SPS-Variablen zuweisen ââââââââââââââââââââââââââââ
obj.linkedVar = var1; // Primäre SPS-Variable
obj.linkedVarExt = var2; // Zweite Variable (z.B. Zylinder-Ausfahren)
obj.linkedVarRet = var2; // Alias fĂźr "RĂźckzug" bei Zylindern (oft = var2)
obj.linkedVar3 = var3; // Dritte Variable (z.B. Greifer)
// Achsen-Steuerung (z.B. Portalachse):
obj.linkedVarX_Plus = var1; obj.linkedVarX_Minus = var2;
obj.linkedVarY_Plus = var3; obj.linkedVarY_Minus = '...';
obj.linkedVarGrab = '...'; // Greifer-Signal
// ââ Pflicht: GrĂśĂe ââââââââââââââââââââââââââââââââââââââââââââââ
obj.width = 40; // Breite in Pixeln
obj.height = 40; // HĂśhe in Pixeln
// ââ DOM-Element aufbauen ââââââââââââââââââââââââââââââââââââââââ
el.className = 'f2d-obj';
el.innerHTML = 'đľ'; // oder komplexes HTML
el.style.fontSize = '24px';
el.style.background = color || '#4caf50'; // color ist bereits Hex!
// â DOM-Referenzen HIER cachen, nicht in animate() per querySelector suchen!
obj.armEl = el.querySelector('.mein-arm'); // Einmalig â kein querySelector pro Tick
obj.ledEl = el.querySelector('.mein-led');
// ââ Physik-Eigenschaften ââââââââââââââââââââââââââââââââââââââââ
obj.useCollision = true; // Objekt wirkt als Hindernis fĂźr WerkstĂźcke
obj.isMetallic = true; // Wird von Magnet-Greifer erfasst
obj.isActive = false; // Allgemeiner Aktiv-Zustand (eigene Nutzung)
// ââ Sonstiges ââââââââââââââââââââââââââââââââââââââââââââââââââ
obj.color = color; // Hex-Farbe speichern (fßr spätere Nutzung in animate)
obj.interval = interval;
obj.label = var1; // Angezeigter Label-Text
obj.audioNodes = null; // Web Audio API (intern)
}
// Wird jeden Simulations-Tick aufgerufen (~20 ms Intervall).
// spsState â { 'VARIABLENNAME': true/false, ⌠}
//
// â ď¸ Kein querySelector() hier â DOM-Referenzen in build() als obj.xxxEl cachen!
// â ď¸ spsState ist NUR hier verfĂźgbar â NICHT als window.spsState!
// â ď¸ resolveLinked() statt spsState[obj.linkedVar] verwenden (â Formel-Support)!
animate: function(obj, spsState) {
const aktiv = resolveLinked(obj, spsState); // â
unterstĂźtzt auch linkedFormula
// Gecachte DOM-Referenz aus build() verwenden:
if (obj.armEl) obj.armEl.style.transform = aktiv ? 'rotate(90deg)' : 'rotate(0deg)';
if (obj.ledEl) obj.ledEl.style.background = aktiv ? '#0f0' : '#333';
obj.el.style.opacity = aktiv ? '1' : '0.3';
// Mehrere Variablen:
const v2 = !!spsState[obj.linkedVarExt];
const v3 = !!spsState[obj.linkedVar3];
}
// Wird fĂźr jedes bewegliche WerkstĂźck aufgerufen das sich im Bereich des Objekts befindet.
// obj â das Factory-Objekt (z.B. FĂśrderer)
// item â das bewegliche WerkstĂźck
// isRunning â ob der FĂśrderer/Aktor aktiv ist (boolean)
// rad â Rotation des Objekts in Radiant
physics: function(obj, item, isRunning, rad) {
if (!isRunning) return;
const speed = 1.5;
item.x += Math.cos(rad) * speed;
item.y += Math.sin(rad) * speed;
}
// Wird beim Rechtsklick auf das Objekt aufgerufen.
// Hier kÜnnen eigene Einträge ans Kontextmenß angehängt werden.
// ctxMenu â das DOM-Element des KontextmenĂźs
// saveCallback â nach Ănderung aufrufen um den Zustand zu speichern
//
// â ď¸ spsState ist hier NICHT verfĂźgbar (kein Parameter, kein window.spsState)!
// Nur obj-Eigenschaften lesen/schreiben â animate() kĂźmmert sich um die Darstellung.
contextMenu: function(obj, ctxMenu, saveCallback) {
// Einfacher Schalter:
const btn = document.createElement('div');
btn.className = 'ctx-item';
btn.textContent = obj.meineSetting ? 'â
Option an' : 'âď¸ Option aus';
btn.onclick = () => { obj.meineSetting = !obj.meineSetting; saveCallback(); };
ctxMenu.appendChild(btn);
// Input-Feld (Zahl):
const wrap = document.createElement('div');
wrap.style.padding = '5px 15px';
wrap.onclick = e => e.stopPropagation();
wrap.innerHTML = '<div style="color:#aaa;font-size:10px;">Wert (0â100)</div>';
const inp = document.createElement('input');
inp.type = 'number'; inp.min = '0'; inp.max = '100'; inp.value = obj.meineZahl || 50;
inp.style.cssText = 'width:100%;padding:4px;background:#1e1e1e;color:#fff;border:1px solid #555;border-radius:3px;font-size:11px;box-sizing:border-box;';
inp.onchange = e => { obj.meineZahl = parseInt(e.target.value); saveCallback(); };
wrap.appendChild(inp); ctxMenu.appendChild(wrap);
}
// Ăberschreibt die Standard-Kollisionserkennung fĂźr diesen Sensor.
// Nur fĂźr Sensor-Plugins relevant (role: 'input').
// targetObj â das zu prĂźfende Objekt (WerkstĂźck oder Factory-Objekt)
// sensorObj â das Sensor-Objekt selbst
// lx,ly â linke obere Ecke des Sensor-Bereichs (Weltkoordinaten)
// lw,lh â Breite und HĂśhe des Sensor-Bereichs
// RĂźckgabe: true wenn Sensor auslĂśst, sonst false
customSensorHit: function(targetObj, sensorObj, lx, ly, lw, lh) {
if (!targetObj.isMetallic) return false; // Nur metallische Objekte erkennen
const tx = targetObj.x, ty = targetObj.y;
return tx >= lx && tx <= lx + lw && ty >= ly && ty <= ly + lh;
}
obj.type // Plugin-SchlĂźssel (z.B. 'mein_plugin') obj.el // DOM-Element (nach build() gesetzt) obj.x, obj.y // Position in der 2D-Anlage (Pixel) â von VALIS verwaltet! obj.rotation // Absolute Rotation in Grad (0/90/180/270) â von VALIS verwaltet! obj.width, obj.height // GrĂśĂe (Pixel) â in build() setzen! VALIS setzt el.style.width/height auto. obj.linkedVar // Primäre SPS-Variable (string) obj.linkedFormula // Formel-Ausdruck (z.B. 'E1 AND NOT E2'), gesetzt wenn Nutzer Formel eingibt obj._compiledFormula // intern gecachte JS-Funktion (von resolveLinked() automatisch erzeugt) obj.useCollision // true â Physik-Engine berĂźcksichtigt als Hindernis fĂźr WerkstĂźcke obj.isMetallic // true â von Magnet-Greifer erfassbar obj.isRunning // (FĂśrderer) ob gerade aktiv â wird als 3. Parameter an physics() Ăźbergeben obj.isActive // allg. Aktiv-Zustand (frei verwendbar, z.B. fĂźr physics() â canMount) obj.audioNodes // Web Audio API Knoten (intern) // ââ Montage ââ (von VALIS gesetzt) obj.children // Array der montierten Kind-Objekte (leer wenn kein canMount) obj.parent // Elternobjekt (Zylinder oder canMount-Plugin), an dem dieses montiert ist obj.attachmentType // 'rod' = an Zylinder-Kolben | 'mount' = an canMount-Plugin
// Jedes Plugin KANN standardmäĂig per Rechtsklick â "An Kolben montieren" // an einen nahe gelegenen Zylinder gehängt werden â keine extra Code nĂśtig! // VALIS bewegt das Plugin-Objekt linear mit der Kolbenstange mit. // // Montage an Zylinder verhindern: noRodAttach: true // kein "An Kolben montieren" im KontextmenĂź // Nach der Montage setzt VALIS automatisch: // obj.parent = <Zylinder-Objekt> // obj.attachmentType = 'rod' // obj.el.classList â enthält 'is-child' (Rotier-Anfasser ausgeblendet) // VALIS speichert/lädt die Verbindung automatisch â kein Plugin-Code nĂśtig.
// Ein Plugin mit canMount: true wird im KontextmenĂź von nahe gelegenen Objekten
// als Montage-Ziel angeboten: Rechtsklick â "đŠ An [Name] montieren"
//
// Nach der Montage setzt VALIS:
// child.parent = <Plugin-Objekt>
// child.attachmentType = 'mount'
// child.el.classList â enthält 'is-child'
//
// â ď¸ VALIS bewegt 'mount'-Kinder NICHT automatisch â das Plugin ist zuständig!
// In animate() obj.children iterieren und Kind-Positionen manuell setzen,
// dann window.updateObjTransform(child) aufrufen.
//
// Beispiel: Drehaktor mit montiertem Greifer/Werkzeug
canMount: true, // im Plugin-Objekt-Literal setzen
// In animate() des Eltern-Plugins:
animate: function(obj, spsState) {
const aktiv = resolveLinked(obj, spsState);
const winkelDeg = (obj.rotation || 0) + (aktiv ? (obj.inverted ? -90 : 90) : 0);
const winkelRad = winkelDeg * Math.PI / 180;
// Arm-Darstellung (wie gehabt)âŚ
if (obj.armEl) obj.armEl.style.transform = `rotate(${aktiv ? 90 : 0}deg)`;
// Montierte Kinder mitbewegen:
if (obj.children) {
const armLen = 35; // Abstand vom Drehpunkt zum Kind
obj.children.forEach(child => {
if (child.attachmentType !== 'mount') return;
// Drehpunkt liegt bei obj.x + 30, obj.y + 24 (Basis-Mittelpunkt)
child.x = obj.x + 30 + Math.cos(winkelRad) * armLen - child.width / 2;
child.y = obj.y + 24 + Math.sin(winkelRad) * armLen - child.height / 2;
child.rotation = winkelDeg;
window.updateObjTransform(child); // â
DOM-Position aktualisieren
});
}
},
// Immer resolveLinked() statt spsState[obj.linkedVar] verwenden! // UnterstĂźtzt: AND, OR, NOT, XOR, Klammerungen, TRUE/FALSE // Nur verfĂźgbar als Argument in animate() â NICHT in contextMenu()! const aktiv = resolveLinked(obj, spsState); // Beispiel-Formeln die Nutzer eingeben kĂśnnen: // LAMPE := Q1 AND NOT STOP // MOTOR := (S1 OR S2) AND NOT NOTHALT // E1 (einfache Variable, kein :=)
â role: 'actuator' â UngĂźltig! Nutze role: 'output' â role: 'sensor' â UngĂźltig! Nutze role: 'input' â window.spsState â spsState ist nur in animate() als Parameter verfĂźgbar â querySelector() in animate() pro Tick â Stattdessen in build() als obj.xxxEl cachen â el.style.position = 'absolute' â Position wird von VALIS verwaltet, nicht setzen! â el.style.width/height in build() manuell setzen â nicht nĂśtig, VALIS setzt es nach build() auto! â el.style.position = 'absolute' setzen â wird von VALIS verwaltet, nicht setzen! â Kind-Divs in el ohne noContainerStyle â f2d-obj Box-Shadow erscheint als sichtbares Quadrat â Fix: noContainerStyle: true im Plugin setzen (entfernt Hintergrund + Shadow vom Container) â canMount: true ohne Kind-Positionierung in animate() â montierte Objekte bewegen sich nicht mit â window.updateObjTransform(child) vergessen â child.x/y/rotation geändert, DOM bleibt an alter Stelle â obj.rot verwendet statt obj.rotation â Eigenschaft heiĂt 'rotation' (Grad, 0/90/180/270) â menuHtml-SchlĂźssel stimmt nicht mit Dateiname Ăźberein â Objekt lässt sich nicht einfĂźgen â role: 'actuator' oder 'sensor' â ungĂźltig! Nur 'output' (Aktor) und 'input' (Sensor) â window.spsState in contextMenu â spsState ist NUR in animate() als Parameter verfĂźgbar
({
meta: { name: 'Signalleuchte', icon: 'đĄ', desc: 'Einfache Signallampe' },
i18n: { de: { name: 'Signalleuchte', desc: 'Leuchtet wenn Variable aktiv' } },
menuTarget: '#menu-aktoren',
// SchlĂźssel 'signalleuchte' muss dem Dateinamen 'signalleuchte.js' entsprechen!
menuHtml: '<div onclick="addFactoryDevice(\'signalleuchte\')">đĄ Signalleuchte</div>',
modalConfig: { t: 'Ausgang:', l1: 'Variable', out: true },
role: 'output', // â
'output' fĂźr Aktoren (nicht 'actuator'!)
color: '#ffeb3b',
build: function(obj, el, var1, var2, var3, color, interval) {
obj.linkedVar = var1;
obj.width = 36;
obj.height = 36;
obj.color = color; // Hex-Farbe merken
el.className = 'f2d-obj';
el.style.cssText = 'font-size:28px;text-align:center;line-height:36px;';
el.innerHTML = 'âŤ';
// â
Kein obj.xxxEl nĂśtig hier â el selbst wird direkt beschrieben
},
animate: function(obj, spsState) {
// â
resolveLinked statt spsState[obj.linkedVar]
// â
spsState ist NUR hier als Parameter verfĂźgbar
const an = resolveLinked(obj, spsState);
obj.el.innerHTML = an ? 'đĄ' : 'âŤ';
obj.el.style.filter = an ? 'drop-shadow(0 0 6px #ff0)' : 'none';
obj.el.style.opacity = an ? '1' : '0.4';
},
contextMenu: function(obj, ctxMenu, saveCallback) {
// â
Kein spsState hier â nur obj-Eigenschaften ändern
const btn = document.createElement('div');
btn.className = 'ctx-item';
btn.textContent = 'đ¨ Farbe wechseln';
btn.onclick = () => {
obj.color = obj.color === '#ffeb3b' ? '#f44336' : '#ffeb3b';
saveCallback(); // Zustand speichern â animate() aktualisiert die Darstellung
};
ctxMenu.appendChild(btn);
}
})
â ď¸ Plugin-Dateien fĂźhren JavaScript aus. Nur eigene oder vertrauenswĂźrdige Dateien hochladen.
Generierter Function Block â in TIA Portal Ăźber âSCL-Quelle importieren" einbinden
Bettet VALIS mit dem aktuellen Zustand in Moodle, ILIAS oder andere Plattformen ein
Lokale Snapshots des aktuellen Standes â gespeichert im Browser
Gespeicherte Anlagen-Bausteine â Rechtsklick auf Objekte â âAls Snippet speichern"