erste Funktionierende Fassung
This commit is contained in:
@@ -48,23 +48,23 @@ if not exist "%PV_CLIENT%" mkdir "%PV_CLIENT%"
|
||||
if not exist "%PV_SERVER%" mkdir "%PV_SERVER%"
|
||||
|
||||
REM Umgebungsvariablen anzeigen
|
||||
echo.
|
||||
echo ================================================================
|
||||
echo PROJECT = %PROJECT%
|
||||
echo PV_BIN = %PV_BIN%
|
||||
echo PV_CFG = %PV_CFG%
|
||||
echo PV_LIB = %PV_LIB%
|
||||
echo PV_DATA = %PV_DATA%
|
||||
echo PV_RESULTS = %PV_RESULTS%
|
||||
echo PV_LOG = %PV_LOG%
|
||||
echo PV_EXAMPLES = %PV_EXAMPLES%
|
||||
echo PV_CLIENT = %PV_CLIENT%
|
||||
echo PV_SERVER = %PV_SERVER%
|
||||
echo PV_SERVER_URL = %PV_SERVER_URL%
|
||||
echo PV_CLIENT_URL = %PV_CLIENT_URL%
|
||||
echo PYTHONPATH = %PYTHONPATH%
|
||||
echo ================================================================
|
||||
echo.
|
||||
REM echo.
|
||||
REM echo ================================================================
|
||||
REM echo PROJECT = %PROJECT%
|
||||
REM echo PV_BIN = %PV_BIN%
|
||||
REM echo PV_CFG = %PV_CFG%
|
||||
REM echo PV_LIB = %PV_LIB%
|
||||
REM echo PV_DATA = %PV_DATA%
|
||||
REM echo PV_RESULTS = %PV_RESULTS%
|
||||
REM echo PV_LOG = %PV_LOG%
|
||||
REM echo PV_EXAMPLES = %PV_EXAMPLES%
|
||||
REM echo PV_CLIENT = %PV_CLIENT%
|
||||
REM echo PV_SERVER = %PV_SERVER%
|
||||
REM echo PV_SERVER_URL = %PV_SERVER_URL%
|
||||
REM echo PV_CLIENT_URL = %PV_CLIENT_URL%
|
||||
REM echo PYTHONPATH = %PYTHONPATH%
|
||||
REM echo ================================================================
|
||||
REM echo.
|
||||
|
||||
REM Optionally keep window open
|
||||
if "%1"=="--keep-open" pause
|
||||
|
||||
4653
client/package-lock.json
generated
Normal file
4653
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,18 @@
|
||||
import { Tldraw } from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import './shapes/shape-types' // Type-Augmentation für Custom Shapes
|
||||
import { PufferShapeUtil } from './shapes/PufferShapeUtil'
|
||||
import { KreiselShapeUtil } from './shapes/KreiselShapeUtil'
|
||||
import { ShapePanel } from './components/ShapePanel'
|
||||
|
||||
const customShapeUtils = [PufferShapeUtil, KreiselShapeUtil]
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw />
|
||||
<Tldraw shapeUtils={customShapeUtils}>
|
||||
<ShapePanel />
|
||||
</Tldraw>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
131
client/src/components/ShapePanel.tsx
Normal file
131
client/src/components/ShapePanel.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { useEditor } from 'tldraw'
|
||||
import { useCallback } from 'react'
|
||||
import { PUFFER_CONFIG, KREISEL_CONFIG } from '../shapes/shape-config'
|
||||
|
||||
/** Nächste freie Nummer für einen Shape-Typ ermitteln. */
|
||||
function nextNumber(editor: ReturnType<typeof useEditor>, type: string): number {
|
||||
const existing = editor.getCurrentPageShapes().filter((s) => s.type === type)
|
||||
// Höchste bestehende Nummer finden
|
||||
let max = 0
|
||||
for (const s of existing) {
|
||||
const lbl = (s.props as { label?: string }).label ?? ''
|
||||
const m = lbl.match(/_(\d+)$/)
|
||||
if (m) max = Math.max(max, parseInt(m[1], 10))
|
||||
}
|
||||
return max + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel links oben: Buttons zum Erstellen von Symbolen + Sync zum Server.
|
||||
* Wird als Kind von <Tldraw> gerendert, daher ist useEditor() verfügbar.
|
||||
*/
|
||||
export function ShapePanel() {
|
||||
const editor = useEditor()
|
||||
|
||||
const createPuffer = useCallback(() => {
|
||||
const nr = nextNumber(editor, 'puffer')
|
||||
const center = editor.getViewportScreenCenter()
|
||||
const point = editor.screenToPage(center)
|
||||
editor.createShape({
|
||||
type: 'puffer',
|
||||
x: point.x - PUFFER_CONFIG.defaultW / 2,
|
||||
y: point.y - PUFFER_CONFIG.defaultH / 2,
|
||||
props: {
|
||||
w: PUFFER_CONFIG.defaultW,
|
||||
h: PUFFER_CONFIG.defaultH,
|
||||
label: `Puffer_${nr}`,
|
||||
},
|
||||
})
|
||||
}, [editor])
|
||||
|
||||
const createKreisel = useCallback(() => {
|
||||
const nr = nextNumber(editor, 'kreisel')
|
||||
const center = editor.getViewportScreenCenter()
|
||||
const point = editor.screenToPage(center)
|
||||
const totalW = 2 * KREISEL_CONFIG.defaultRadius + KREISEL_CONFIG.defaultAbstand
|
||||
const totalH = 2 * KREISEL_CONFIG.defaultRadius
|
||||
editor.createShape({
|
||||
type: 'kreisel',
|
||||
x: point.x - totalW / 2,
|
||||
y: point.y - totalH / 2,
|
||||
props: {
|
||||
abstand: KREISEL_CONFIG.defaultAbstand,
|
||||
radius: KREISEL_CONFIG.defaultRadius,
|
||||
label: `Kreisel_${nr}`,
|
||||
},
|
||||
})
|
||||
}, [editor])
|
||||
|
||||
const syncToServer = useCallback(async () => {
|
||||
const allShapes = editor.getCurrentPageShapes()
|
||||
|
||||
const puffer = allShapes
|
||||
.filter((s) => s.type === 'puffer')
|
||||
.map((s) => {
|
||||
const p = s.props as { w: number; h: number }
|
||||
return { id: s.id, x: s.x, y: s.y, w: p.w, h: p.h }
|
||||
})
|
||||
|
||||
const kreisel = allShapes
|
||||
.filter((s) => s.type === 'kreisel')
|
||||
.map((s) => {
|
||||
const p = s.props as { abstand: number; radius: number }
|
||||
return { id: s.id, x: s.x, y: s.y, abstand: p.abstand, radius: p.radius }
|
||||
})
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/shapes/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puffer, kreisel }),
|
||||
})
|
||||
const data = await res.json()
|
||||
console.log('Server response:', data)
|
||||
alert(`Sync OK – ${data.received.puffer_count} Puffer, ${data.received.kreisel_count} Kreisel`)
|
||||
} catch (err) {
|
||||
console.error('Sync error:', err)
|
||||
alert('Sync fehlgeschlagen – siehe Konsole')
|
||||
}
|
||||
}, [editor])
|
||||
|
||||
return (
|
||||
<div style={panelStyle}>
|
||||
<div style={{ fontWeight: 'bold', fontSize: 13, marginBottom: 4 }}>
|
||||
Symbole
|
||||
</div>
|
||||
<button onClick={createPuffer} style={btnStyle}>
|
||||
▭ Puffer
|
||||
</button>
|
||||
<button onClick={createKreisel} style={btnStyle}>
|
||||
◎ Kreisel
|
||||
</button>
|
||||
<hr style={{ width: '100%', margin: '4px 0' }} />
|
||||
<button onClick={syncToServer} style={{ ...btnStyle, background: '#e0f0ff' }}>
|
||||
↑ Sync Server
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const panelStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
top: 60,
|
||||
left: 10,
|
||||
zIndex: 1000,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 6,
|
||||
background: 'white',
|
||||
padding: 10,
|
||||
borderRadius: 8,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
||||
}
|
||||
|
||||
const btnStyle: React.CSSProperties = {
|
||||
padding: '6px 12px',
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: 4,
|
||||
background: '#f8f8f8',
|
||||
fontSize: 13,
|
||||
}
|
||||
142
client/src/shapes/KreiselShapeUtil.tsx
Normal file
142
client/src/shapes/KreiselShapeUtil.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
ShapeUtil,
|
||||
TLHandle,
|
||||
TLShapePartial,
|
||||
Rectangle2d,
|
||||
HTMLContainer,
|
||||
IndexKey,
|
||||
} from 'tldraw'
|
||||
import { KreiselShape, kreiselShapeProps } from './shape-types'
|
||||
import { KREISEL_CONFIG as CFG } from './shape-config'
|
||||
|
||||
/**
|
||||
* Kreisel: Zwei Kreise (links/rechts) mit tangentialen Verbindungslinien.
|
||||
* Handles im Zentrum beider Kreise zum Ändern des Abstands.
|
||||
*/
|
||||
export class KreiselShapeUtil extends ShapeUtil<KreiselShape> {
|
||||
static override type = 'kreisel' as const
|
||||
static override props = kreiselShapeProps
|
||||
|
||||
getDefaultProps(): KreiselShape['props'] {
|
||||
return { abstand: CFG.defaultAbstand, radius: CFG.defaultRadius, label: 'Kreisel' }
|
||||
}
|
||||
|
||||
getGeometry(shape: KreiselShape) {
|
||||
const { abstand, radius } = shape.props
|
||||
return new Rectangle2d({
|
||||
width: 2 * radius + abstand,
|
||||
height: 2 * radius,
|
||||
isFilled: false,
|
||||
})
|
||||
}
|
||||
|
||||
override canResize() {
|
||||
return false
|
||||
}
|
||||
|
||||
override onRotate(initial: KreiselShape, current: KreiselShape): TLShapePartial<KreiselShape> {
|
||||
const HALF_PI = Math.PI / 2
|
||||
const snapped = Math.round(current.rotation / HALF_PI) * HALF_PI
|
||||
return { id: current.id, type: 'kreisel', x: initial.x, y: initial.y, rotation: snapped }
|
||||
}
|
||||
|
||||
override getHandles(shape: KreiselShape): TLHandle[] {
|
||||
const { abstand, radius } = shape.props
|
||||
return [
|
||||
{
|
||||
id: 'left',
|
||||
type: 'vertex',
|
||||
x: radius,
|
||||
y: radius,
|
||||
index: 'a1' as IndexKey,
|
||||
canSnap: true,
|
||||
},
|
||||
{
|
||||
id: 'right',
|
||||
type: 'vertex',
|
||||
x: radius + abstand,
|
||||
y: radius,
|
||||
index: 'a2' as IndexKey,
|
||||
canSnap: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
override onHandleDrag(
|
||||
shape: KreiselShape,
|
||||
{ handle }: { handle: TLHandle }
|
||||
): TLShapePartial<KreiselShape> | void {
|
||||
const r = shape.props.radius
|
||||
|
||||
if (handle.id === 'right') {
|
||||
const newAbstand = Math.min(CFG.maxAbstand, Math.max(CFG.minAbstand, handle.x - r))
|
||||
return {
|
||||
id: shape.id,
|
||||
type: 'kreisel',
|
||||
props: { ...shape.props, abstand: newAbstand },
|
||||
}
|
||||
}
|
||||
|
||||
if (handle.id === 'left') {
|
||||
const oldRightX = r + shape.props.abstand
|
||||
const raw = oldRightX - handle.x
|
||||
const newAbstand = Math.min(CFG.maxAbstand, Math.max(CFG.minAbstand, raw))
|
||||
const dx = shape.props.abstand - newAbstand
|
||||
return {
|
||||
id: shape.id,
|
||||
type: 'kreisel',
|
||||
x: shape.x + dx,
|
||||
props: { ...shape.props, abstand: newAbstand },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component(shape: KreiselShape) {
|
||||
const { abstand, radius: r } = shape.props
|
||||
const totalW = 2 * r + abstand
|
||||
const totalH = 2 * r
|
||||
|
||||
return (
|
||||
<HTMLContainer>
|
||||
<svg
|
||||
width={totalW}
|
||||
height={totalH}
|
||||
style={{ overflow: 'visible' }}
|
||||
>
|
||||
{/* Linker Kreis */}
|
||||
<circle cx={r} cy={r} r={r} fill="none" stroke="black" strokeWidth={2} />
|
||||
{/* Rechter Kreis */}
|
||||
<circle cx={r + abstand} cy={r} r={r} fill="none" stroke="black" strokeWidth={2} />
|
||||
{/* Obere Tangentiallinie */}
|
||||
<line x1={r} y1={0} x2={r + abstand} y2={0} stroke="black" strokeWidth={2} />
|
||||
{/* Untere Tangentiallinie */}
|
||||
<line x1={r} y1={totalH} x2={r + abstand} y2={totalH} stroke="black" strokeWidth={2} />
|
||||
{/* Label */}
|
||||
<text
|
||||
x={totalW / 2}
|
||||
y={r}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="central"
|
||||
fontSize={36}
|
||||
fill="#666"
|
||||
style={{ pointerEvents: 'none', userSelect: 'none' }}
|
||||
>
|
||||
{shape.props.label}
|
||||
</text>
|
||||
</svg>
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: KreiselShape) {
|
||||
const { abstand, radius: r } = shape.props
|
||||
return (
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={2 * r + abstand}
|
||||
height={2 * r}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
127
client/src/shapes/PufferShapeUtil.tsx
Normal file
127
client/src/shapes/PufferShapeUtil.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
ShapeUtil,
|
||||
TLHandle,
|
||||
TLShapePartial,
|
||||
Rectangle2d,
|
||||
HTMLContainer,
|
||||
IndexKey,
|
||||
} from 'tldraw'
|
||||
import { PufferShape, pufferShapeProps } from './shape-types'
|
||||
import { PUFFER_CONFIG as CFG } from './shape-config'
|
||||
|
||||
/** Puffer: Rechteck mit Handles oben-mittig (Höhe) und rechts-mittig (Breite) */
|
||||
export class PufferShapeUtil extends ShapeUtil<PufferShape> {
|
||||
static override type = 'puffer' as const
|
||||
static override props = pufferShapeProps
|
||||
|
||||
getDefaultProps(): PufferShape['props'] {
|
||||
return { w: CFG.defaultW, h: CFG.defaultH, label: 'Puffer' }
|
||||
}
|
||||
|
||||
getGeometry(shape: PufferShape) {
|
||||
return new Rectangle2d({
|
||||
width: shape.props.w,
|
||||
height: shape.props.h,
|
||||
isFilled: false,
|
||||
})
|
||||
}
|
||||
|
||||
override canResize() {
|
||||
return false
|
||||
}
|
||||
|
||||
override onRotate(initial: PufferShape, current: PufferShape): TLShapePartial<PufferShape> {
|
||||
const HALF_PI = Math.PI / 2
|
||||
const snapped = Math.round(current.rotation / HALF_PI) * HALF_PI
|
||||
return { id: current.id, type: 'puffer', x: initial.x, y: initial.y, rotation: snapped }
|
||||
}
|
||||
|
||||
override getHandles(shape: PufferShape): TLHandle[] {
|
||||
return [
|
||||
{
|
||||
id: 'top',
|
||||
type: 'vertex',
|
||||
x: shape.props.w / 2,
|
||||
y: 0,
|
||||
index: 'a1' as IndexKey,
|
||||
canSnap: true,
|
||||
},
|
||||
{
|
||||
id: 'right',
|
||||
type: 'vertex',
|
||||
x: shape.props.w,
|
||||
y: shape.props.h / 2,
|
||||
index: 'a2' as IndexKey,
|
||||
canSnap: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
override onHandleDrag(
|
||||
shape: PufferShape,
|
||||
{ handle }: { handle: TLHandle }
|
||||
): TLShapePartial<PufferShape> | void {
|
||||
if (handle.id === 'top') {
|
||||
const newH = Math.min(CFG.maxH, Math.max(CFG.minH, shape.props.h - handle.y))
|
||||
return {
|
||||
id: shape.id,
|
||||
type: 'puffer',
|
||||
y: shape.y + (shape.props.h - newH),
|
||||
props: { ...shape.props, h: newH },
|
||||
}
|
||||
}
|
||||
if (handle.id === 'right') {
|
||||
const newW = Math.min(CFG.maxW, Math.max(CFG.minW, handle.x))
|
||||
return {
|
||||
id: shape.id,
|
||||
type: 'puffer',
|
||||
props: { ...shape.props, w: newW },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component(shape: PufferShape) {
|
||||
const { w, h } = shape.props
|
||||
return (
|
||||
<HTMLContainer>
|
||||
<svg
|
||||
width={w}
|
||||
height={h}
|
||||
style={{ overflow: 'visible' }}
|
||||
>
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={w}
|
||||
height={h}
|
||||
fill="none"
|
||||
stroke="#2563eb"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<text
|
||||
x={w / 2}
|
||||
y={h / 2}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="central"
|
||||
fontSize={36}
|
||||
fill="#666"
|
||||
style={{ pointerEvents: 'none', userSelect: 'none' }}
|
||||
>
|
||||
{shape.props.label}
|
||||
</text>
|
||||
</svg>
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: PufferShape) {
|
||||
return (
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={shape.props.w}
|
||||
height={shape.props.h}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
26
client/src/shapes/shape-config.ts
Normal file
26
client/src/shapes/shape-config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Zentrale Konfiguration für alle Custom Shapes.
|
||||
* Defaults, Minima und Maxima an einer Stelle gepflegt.
|
||||
*/
|
||||
|
||||
export const PUFFER_CONFIG = {
|
||||
// Defaults
|
||||
defaultW: 120,
|
||||
defaultH: 60,
|
||||
// Minima
|
||||
minW: 20,
|
||||
minH: 20,
|
||||
// Maxima
|
||||
maxW: 500,
|
||||
maxH: 300,
|
||||
}
|
||||
|
||||
export const KREISEL_CONFIG = {
|
||||
// Defaults
|
||||
defaultAbstand: 200,
|
||||
defaultRadius: 80,
|
||||
// Minima
|
||||
minAbstand: 20,
|
||||
// Maxima
|
||||
maxAbstand: 4000,
|
||||
}
|
||||
39
client/src/shapes/shape-types.ts
Normal file
39
client/src/shapes/shape-types.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { T, TLBaseShape } from 'tldraw'
|
||||
|
||||
// --- Puffer ---
|
||||
export type PufferShapeProps = {
|
||||
w: number
|
||||
h: number
|
||||
label: string
|
||||
}
|
||||
|
||||
export type PufferShape = TLBaseShape<'puffer', PufferShapeProps>
|
||||
|
||||
export const pufferShapeProps = {
|
||||
w: T.number,
|
||||
h: T.number,
|
||||
label: T.string,
|
||||
}
|
||||
|
||||
// --- Kreisel ---
|
||||
export type KreiselShapeProps = {
|
||||
abstand: number
|
||||
radius: number
|
||||
label: string
|
||||
}
|
||||
|
||||
export type KreiselShape = TLBaseShape<'kreisel', KreiselShapeProps>
|
||||
|
||||
export const kreiselShapeProps = {
|
||||
abstand: T.number,
|
||||
radius: T.number,
|
||||
label: T.string,
|
||||
}
|
||||
|
||||
// --- tldraw Type-Augmentation: Custom Shapes registrieren ---
|
||||
declare module '@tldraw/tlschema' {
|
||||
interface TLGlobalShapePropsMap {
|
||||
puffer: PufferShapeProps
|
||||
kreisel: KreiselShapeProps
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ PlantPlan FastAPI Backend
|
||||
- Symbolbibliothek ausliefern
|
||||
- Koordinaten normalisieren
|
||||
- JSON/SVG Export
|
||||
- Shape-Änderungen empfangen (Puffer + Kreisel)
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -24,14 +25,42 @@ app.add_middleware(
|
||||
CATALOG_DIR = Path(__file__).parent / "catalog"
|
||||
|
||||
|
||||
# --- Models ---
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pydantic Models
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class SketchPayload(BaseModel):
|
||||
shapes: list[dict]
|
||||
grid_size: int = 10
|
||||
|
||||
|
||||
# --- Endpoints ---
|
||||
class PufferUpdate(BaseModel):
|
||||
"""Puffer-Symbol: Rechteck mit Breite und Höhe."""
|
||||
id: str
|
||||
x: float
|
||||
y: float
|
||||
w: float # Breite
|
||||
h: float # Höhe
|
||||
|
||||
|
||||
class KreiselUpdate(BaseModel):
|
||||
"""Kreisel-Symbol: Zwei Kreise mit Abstand und Radius."""
|
||||
id: str
|
||||
x: float
|
||||
y: float
|
||||
abstand: float # Abstand zwischen den Kreismittelpunkten
|
||||
radius: float # Radius beider Kreise
|
||||
|
||||
|
||||
class ShapeUpdatePayload(BaseModel):
|
||||
"""Payload für Shape-Änderungen vom Client."""
|
||||
puffer: list[PufferUpdate] = []
|
||||
kreisel: list[KreiselUpdate] = []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Endpoints
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.get("/symbols")
|
||||
async def get_symbols():
|
||||
@@ -64,3 +93,42 @@ async def export_sketch(payload: SketchPayload):
|
||||
"shapes": payload.shapes,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@app.post("/shapes/update")
|
||||
async def update_shapes(payload: ShapeUpdatePayload):
|
||||
"""
|
||||
Shape-Änderungen vom Client empfangen.
|
||||
|
||||
Gibt für jeden Puffer Breite/Höhe und für jeden Kreisel
|
||||
Abstand/Radius + berechnete Gesamtlänge zurück.
|
||||
"""
|
||||
result = {
|
||||
"received": {
|
||||
"puffer_count": len(payload.puffer),
|
||||
"kreisel_count": len(payload.kreisel),
|
||||
},
|
||||
"puffer": [],
|
||||
"kreisel": [],
|
||||
}
|
||||
|
||||
for p in payload.puffer:
|
||||
result["puffer"].append({
|
||||
"id": p.id,
|
||||
"x": p.x,
|
||||
"y": p.y,
|
||||
"breite": p.w,
|
||||
"hoehe": p.h,
|
||||
})
|
||||
|
||||
for k in payload.kreisel:
|
||||
result["kreisel"].append({
|
||||
"id": k.id,
|
||||
"x": k.x,
|
||||
"y": k.y,
|
||||
"abstand": k.abstand,
|
||||
"radius": k.radius,
|
||||
"gesamtlaenge": 2 * k.radius + k.abstand,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user