rocket-welder-sdk 1.1.43__py3-none-any.whl → 1.1.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rocket_welder_sdk/__init__.py +44 -22
- rocket_welder_sdk/binary_frame_reader.py +222 -0
- rocket_welder_sdk/binary_frame_writer.py +213 -0
- rocket_welder_sdk/confidence.py +206 -0
- rocket_welder_sdk/delta_frame.py +150 -0
- rocket_welder_sdk/graphics/__init__.py +42 -0
- rocket_welder_sdk/graphics/layer_canvas.py +157 -0
- rocket_welder_sdk/graphics/protocol.py +72 -0
- rocket_welder_sdk/graphics/rgb_color.py +109 -0
- rocket_welder_sdk/graphics/stage.py +494 -0
- rocket_welder_sdk/graphics/vector_graphics_encoder.py +575 -0
- rocket_welder_sdk/high_level/__init__.py +8 -1
- rocket_welder_sdk/high_level/client.py +114 -3
- rocket_welder_sdk/high_level/connection_strings.py +88 -15
- rocket_welder_sdk/high_level/frame_sink_factory.py +2 -15
- rocket_welder_sdk/high_level/transport_protocol.py +4 -130
- rocket_welder_sdk/keypoints_protocol.py +520 -55
- rocket_welder_sdk/rocket_welder_client.py +210 -89
- rocket_welder_sdk/segmentation_result.py +387 -2
- rocket_welder_sdk/session_id.py +7 -182
- rocket_welder_sdk/transport/__init__.py +10 -3
- rocket_welder_sdk/transport/frame_sink.py +3 -3
- rocket_welder_sdk/transport/frame_source.py +2 -2
- rocket_welder_sdk/transport/websocket_transport.py +316 -0
- rocket_welder_sdk/varint.py +213 -0
- {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.45.dist-info}/METADATA +1 -4
- rocket_welder_sdk-1.1.45.dist-info/RECORD +51 -0
- {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.45.dist-info}/WHEEL +1 -1
- rocket_welder_sdk/transport/nng_transport.py +0 -197
- rocket_welder_sdk-1.1.43.dist-info/RECORD +0 -40
- {rocket_welder_sdk-1.1.43.dist-info → rocket_welder_sdk-1.1.45.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Confidence value type for ML detection results.
|
|
3
|
+
Matches C# Confidence struct from RocketWelder.SDK.Protocols.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import ClassVar, Optional, Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Confidence:
|
|
14
|
+
"""
|
|
15
|
+
Represents a confidence score for ML detection results.
|
|
16
|
+
|
|
17
|
+
Internally stored as ushort (0-65535) for full precision.
|
|
18
|
+
Provides normalized float (0.0-1.0) for easy usage.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> # Create from float (0.0-1.0)
|
|
22
|
+
>>> c1 = Confidence.from_float(0.95)
|
|
23
|
+
>>>
|
|
24
|
+
>>> # Create from raw ushort (0-65535)
|
|
25
|
+
>>> c2 = Confidence(raw=65535)
|
|
26
|
+
>>>
|
|
27
|
+
>>> # Get normalized float
|
|
28
|
+
>>> normalized = c1.normalized # 0.95
|
|
29
|
+
>>>
|
|
30
|
+
>>> # Compare with float
|
|
31
|
+
>>> if c1 > 0.9:
|
|
32
|
+
... print("High confidence")
|
|
33
|
+
>>>
|
|
34
|
+
>>> # Get raw ushort value
|
|
35
|
+
>>> raw_value = c1.raw
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
MAX_RAW: ClassVar[int] = 65535 # ushort.MaxValue
|
|
39
|
+
|
|
40
|
+
raw: int
|
|
41
|
+
"""The raw ushort value (0-65535)."""
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
"""Validate raw value is in valid range."""
|
|
45
|
+
if not 0 <= self.raw <= Confidence.MAX_RAW:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Raw value must be between 0 and {Confidence.MAX_RAW}, got {self.raw}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def normalized(self) -> float:
|
|
52
|
+
"""Get the normalized float value (0.0-1.0)."""
|
|
53
|
+
return self.raw / Confidence.MAX_RAW
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_float(cls, value: float) -> Confidence:
|
|
57
|
+
"""
|
|
58
|
+
Create Confidence from a float value (0.0-1.0).
|
|
59
|
+
|
|
60
|
+
The value is clamped to [0.0, 1.0] range before conversion.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
value: Float value in range 0.0 to 1.0
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Confidence instance
|
|
67
|
+
"""
|
|
68
|
+
clamped = max(0.0, min(1.0, value))
|
|
69
|
+
raw = int(clamped * cls.MAX_RAW)
|
|
70
|
+
return cls(raw=raw)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def full(cls) -> Confidence:
|
|
74
|
+
"""Full confidence (1.0)."""
|
|
75
|
+
return cls(raw=cls.MAX_RAW)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def zero(cls) -> Confidence:
|
|
79
|
+
"""Zero confidence (0.0)."""
|
|
80
|
+
return cls(raw=0)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def parse(cls, value: Union[str, int, float, Confidence]) -> Confidence:
|
|
84
|
+
"""
|
|
85
|
+
Parse a string, number, or Confidence into a Confidence object.
|
|
86
|
+
|
|
87
|
+
Equivalent to C# IParsable<Confidence>.Parse().
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
value: Value to parse (e.g., "0.95", 0.95, 65535)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Confidence instance
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If the value cannot be parsed
|
|
97
|
+
"""
|
|
98
|
+
if isinstance(value, Confidence):
|
|
99
|
+
return value
|
|
100
|
+
|
|
101
|
+
if isinstance(value, int):
|
|
102
|
+
if 0 <= value <= cls.MAX_RAW:
|
|
103
|
+
return cls(raw=value)
|
|
104
|
+
raise ValueError(f"Integer value must be between 0 and {cls.MAX_RAW}, got {value}")
|
|
105
|
+
|
|
106
|
+
if isinstance(value, float):
|
|
107
|
+
return cls.from_float(value)
|
|
108
|
+
|
|
109
|
+
if isinstance(value, str):
|
|
110
|
+
value = value.strip()
|
|
111
|
+
if not value:
|
|
112
|
+
raise ValueError("Cannot parse empty string as Confidence")
|
|
113
|
+
|
|
114
|
+
# Try parsing as percentage (e.g., "95.0%")
|
|
115
|
+
if value.endswith("%"):
|
|
116
|
+
try:
|
|
117
|
+
percent = float(value[:-1])
|
|
118
|
+
return cls.from_float(percent / 100.0)
|
|
119
|
+
except ValueError as e:
|
|
120
|
+
raise ValueError(f"Invalid percentage format: '{value}'") from e
|
|
121
|
+
|
|
122
|
+
# Try parsing as float (0.0-1.0)
|
|
123
|
+
try:
|
|
124
|
+
float_val = float(value)
|
|
125
|
+
if 0.0 <= float_val <= 1.0:
|
|
126
|
+
return cls.from_float(float_val)
|
|
127
|
+
# If > 1.0, treat as raw ushort value
|
|
128
|
+
if float_val == int(float_val) and 0 <= float_val <= cls.MAX_RAW:
|
|
129
|
+
return cls(raw=int(float_val))
|
|
130
|
+
raise ValueError(
|
|
131
|
+
f"Float value must be 0.0-1.0 or integer 0-{cls.MAX_RAW}, got {float_val}"
|
|
132
|
+
)
|
|
133
|
+
except ValueError:
|
|
134
|
+
raise ValueError(f"Invalid Confidence format: '{value}'") from None
|
|
135
|
+
|
|
136
|
+
raise ValueError(f"Cannot parse {type(value).__name__} as Confidence")
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def try_parse(cls, value: Union[str, int, float, Confidence]) -> Optional[Confidence]:
|
|
140
|
+
"""
|
|
141
|
+
Try to parse a value into Confidence, returning None on failure.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
value: Value to parse
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Confidence instance or None if parsing failed
|
|
148
|
+
"""
|
|
149
|
+
try:
|
|
150
|
+
return cls.parse(value)
|
|
151
|
+
except (ValueError, TypeError):
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def __float__(self) -> float:
|
|
155
|
+
"""Convert to float (normalized value)."""
|
|
156
|
+
return self.normalized
|
|
157
|
+
|
|
158
|
+
def __int__(self) -> int:
|
|
159
|
+
"""Convert to int (raw value)."""
|
|
160
|
+
return self.raw
|
|
161
|
+
|
|
162
|
+
def __str__(self) -> str:
|
|
163
|
+
"""Return the normalized value as a percentage string (e.g., '95.0%')."""
|
|
164
|
+
return f"{self.normalized:.1%}"
|
|
165
|
+
|
|
166
|
+
def __repr__(self) -> str:
|
|
167
|
+
"""Developer-friendly representation."""
|
|
168
|
+
return f"Confidence(raw={self.raw}, normalized={self.normalized:.4f})"
|
|
169
|
+
|
|
170
|
+
# Comparison operators with float
|
|
171
|
+
def __lt__(self, other: object) -> bool:
|
|
172
|
+
"""Less than comparison."""
|
|
173
|
+
if isinstance(other, Confidence):
|
|
174
|
+
return self.raw < other.raw
|
|
175
|
+
if isinstance(other, (int, float)):
|
|
176
|
+
return self.normalized < other
|
|
177
|
+
return NotImplemented
|
|
178
|
+
|
|
179
|
+
def __le__(self, other: object) -> bool:
|
|
180
|
+
"""Less than or equal comparison."""
|
|
181
|
+
if isinstance(other, Confidence):
|
|
182
|
+
return self.raw <= other.raw
|
|
183
|
+
if isinstance(other, (int, float)):
|
|
184
|
+
return self.normalized <= other
|
|
185
|
+
return NotImplemented
|
|
186
|
+
|
|
187
|
+
def __gt__(self, other: object) -> bool:
|
|
188
|
+
"""Greater than comparison."""
|
|
189
|
+
if isinstance(other, Confidence):
|
|
190
|
+
return self.raw > other.raw
|
|
191
|
+
if isinstance(other, (int, float)):
|
|
192
|
+
return self.normalized > other
|
|
193
|
+
return NotImplemented
|
|
194
|
+
|
|
195
|
+
def __ge__(self, other: object) -> bool:
|
|
196
|
+
"""Greater than or equal comparison."""
|
|
197
|
+
if isinstance(other, Confidence):
|
|
198
|
+
return self.raw >= other.raw
|
|
199
|
+
if isinstance(other, (int, float)):
|
|
200
|
+
return self.normalized >= other
|
|
201
|
+
return NotImplemented
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# Convenience constants matching C# static properties
|
|
205
|
+
FULL = Confidence.full()
|
|
206
|
+
ZERO = Confidence.zero()
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DeltaFrame generic container for delta-encoded streaming data.
|
|
3
|
+
Matches C# DeltaFrame<T> struct from RocketWelder.SDK.Protocols.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Generic, Iterator, Sequence, TypeVar
|
|
10
|
+
|
|
11
|
+
# TypeVar for the item type (equivalent to C# struct constraint)
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class DeltaFrame(Generic[T]):
|
|
17
|
+
"""
|
|
18
|
+
Generic container for delta-encoded streaming data.
|
|
19
|
+
|
|
20
|
+
Used by Reader/Source classes where IsDelta is needed for decoding.
|
|
21
|
+
When IsDelta is True, the items contain delta-encoded values relative
|
|
22
|
+
to the previous frame, otherwise they contain absolute values.
|
|
23
|
+
|
|
24
|
+
Type Parameters:
|
|
25
|
+
T: The item type (e.g., KeyPoint, SegmentationInstance)
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
frame_id: The frame identifier (ulong equivalent).
|
|
29
|
+
is_delta: True if this frame contains delta-encoded values relative to previous frame.
|
|
30
|
+
items: The items in this frame. Caller owns the backing memory.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
>>> from dataclasses import dataclass
|
|
34
|
+
>>> @dataclass(frozen=True)
|
|
35
|
+
... class KeyPoint:
|
|
36
|
+
... x: int
|
|
37
|
+
... y: int
|
|
38
|
+
...
|
|
39
|
+
>>> # Create a master frame (absolute values)
|
|
40
|
+
>>> master = DeltaFrame[KeyPoint](
|
|
41
|
+
... frame_id=1,
|
|
42
|
+
... is_delta=False,
|
|
43
|
+
... items=[KeyPoint(100, 200), KeyPoint(150, 250)]
|
|
44
|
+
... )
|
|
45
|
+
>>>
|
|
46
|
+
>>> # Create a delta frame (relative values)
|
|
47
|
+
>>> delta = DeltaFrame[KeyPoint](
|
|
48
|
+
... frame_id=2,
|
|
49
|
+
... is_delta=True,
|
|
50
|
+
... items=[KeyPoint(5, -3), KeyPoint(-2, 1)]
|
|
51
|
+
... )
|
|
52
|
+
>>>
|
|
53
|
+
>>> # Check if it's a master frame
|
|
54
|
+
>>> if not delta.is_delta:
|
|
55
|
+
... print("Master frame")
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
frame_id: int
|
|
59
|
+
"""The frame identifier (ulong equivalent, 0 to 2^64-1)."""
|
|
60
|
+
|
|
61
|
+
is_delta: bool
|
|
62
|
+
"""True if this frame contains delta-encoded values relative to previous frame."""
|
|
63
|
+
|
|
64
|
+
items: Sequence[T]
|
|
65
|
+
"""The items in this frame. Caller owns the backing memory."""
|
|
66
|
+
|
|
67
|
+
def __post_init__(self) -> None:
|
|
68
|
+
"""Validate frame_id is non-negative."""
|
|
69
|
+
if self.frame_id < 0:
|
|
70
|
+
raise ValueError(f"frame_id must be non-negative, got {self.frame_id}")
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_master(self) -> bool:
|
|
74
|
+
"""True if this is a master frame (not delta-encoded)."""
|
|
75
|
+
return not self.is_delta
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def count(self) -> int:
|
|
79
|
+
"""Number of items in this frame."""
|
|
80
|
+
return len(self.items)
|
|
81
|
+
|
|
82
|
+
def __len__(self) -> int:
|
|
83
|
+
"""Return the number of items in this frame."""
|
|
84
|
+
return len(self.items)
|
|
85
|
+
|
|
86
|
+
def __bool__(self) -> bool:
|
|
87
|
+
"""Return True if the frame has items."""
|
|
88
|
+
return len(self.items) > 0
|
|
89
|
+
|
|
90
|
+
def __iter__(self) -> Iterator[T]:
|
|
91
|
+
"""Iterate over items in this frame."""
|
|
92
|
+
return iter(self.items)
|
|
93
|
+
|
|
94
|
+
def __getitem__(self, index: int) -> T:
|
|
95
|
+
"""Get item by index."""
|
|
96
|
+
return self.items[index]
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def master(cls, frame_id: int, items: Sequence[T]) -> DeltaFrame[T]:
|
|
100
|
+
"""
|
|
101
|
+
Create a master frame (absolute values, not delta-encoded).
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
frame_id: The frame identifier
|
|
105
|
+
items: The items with absolute values
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
DeltaFrame with is_delta=False
|
|
109
|
+
"""
|
|
110
|
+
return cls(frame_id=frame_id, is_delta=False, items=items)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def delta(cls, frame_id: int, items: Sequence[T]) -> DeltaFrame[T]:
|
|
114
|
+
"""
|
|
115
|
+
Create a delta frame (values relative to previous frame).
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
frame_id: The frame identifier
|
|
119
|
+
items: The items with delta-encoded values
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
DeltaFrame with is_delta=True
|
|
123
|
+
"""
|
|
124
|
+
return cls(frame_id=frame_id, is_delta=True, items=items)
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def empty_master(cls, frame_id: int) -> DeltaFrame[T]:
|
|
128
|
+
"""
|
|
129
|
+
Create an empty master frame.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
frame_id: The frame identifier
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
DeltaFrame with no items and is_delta=False
|
|
136
|
+
"""
|
|
137
|
+
return cls(frame_id=frame_id, is_delta=False, items=[])
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def empty_delta(cls, frame_id: int) -> DeltaFrame[T]:
|
|
141
|
+
"""
|
|
142
|
+
Create an empty delta frame.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
frame_id: The frame identifier
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
DeltaFrame with no items and is_delta=True
|
|
149
|
+
"""
|
|
150
|
+
return cls(frame_id=frame_id, is_delta=True, items=[])
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VectorGraphics module for streaming graphics overlays.
|
|
3
|
+
|
|
4
|
+
This module provides classes for encoding and streaming vector graphics
|
|
5
|
+
to the browser using Protocol V2.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
from rocket_welder_sdk.graphics import StageSink, RgbColor
|
|
9
|
+
from rocket_welder_sdk.transport import UnixSocketFrameSink
|
|
10
|
+
|
|
11
|
+
# Create transport and sink
|
|
12
|
+
sink = UnixSocketFrameSink("/tmp/graphics.sock")
|
|
13
|
+
stage = StageSink(sink)
|
|
14
|
+
|
|
15
|
+
# Draw graphics for each frame
|
|
16
|
+
with stage.create_writer(frame_id) as writer:
|
|
17
|
+
writer[0].set_stroke(RgbColor.Red)
|
|
18
|
+
writer[0].draw_polygon([(0, 0), (100, 0), (100, 100)])
|
|
19
|
+
writer[1].draw_text("Hello", 10, 20)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .layer_canvas import ILayerCanvas
|
|
23
|
+
from .protocol import END_MARKER_BYTE1, END_MARKER_BYTE2, FrameType, OpType, PropertyId
|
|
24
|
+
from .rgb_color import RgbColor
|
|
25
|
+
from .stage import IStageSink, IStageWriter, LayerEncoder, StageSink, StageWriter
|
|
26
|
+
from .vector_graphics_encoder import VectorGraphicsEncoder
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"END_MARKER_BYTE1",
|
|
30
|
+
"END_MARKER_BYTE2",
|
|
31
|
+
"FrameType",
|
|
32
|
+
"ILayerCanvas",
|
|
33
|
+
"IStageSink",
|
|
34
|
+
"IStageWriter",
|
|
35
|
+
"LayerEncoder",
|
|
36
|
+
"OpType",
|
|
37
|
+
"PropertyId",
|
|
38
|
+
"RgbColor",
|
|
39
|
+
"StageSink",
|
|
40
|
+
"StageWriter",
|
|
41
|
+
"VectorGraphicsEncoder",
|
|
42
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ILayerCanvas protocol - per-layer drawing interface.
|
|
3
|
+
|
|
4
|
+
Matches C# ILayerCanvas interface from BlazorBlaze.Server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Protocol, Sequence, Tuple, runtime_checkable
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .rgb_color import RgbColor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class ILayerCanvas(Protocol):
|
|
17
|
+
"""
|
|
18
|
+
Represents a single rendering layer with stateful context management.
|
|
19
|
+
|
|
20
|
+
Mirrors SkiaSharp's canvas API for familiar usage patterns.
|
|
21
|
+
This is a Protocol class (interface) matching C# ILayerCanvas.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def layer_id(self) -> int:
|
|
26
|
+
"""The layer ID (z-order index)."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
# ============== Layer Frame Type ==============
|
|
30
|
+
|
|
31
|
+
def master(self) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Sets this layer to Master mode - clears and redraws with operations that follow.
|
|
34
|
+
|
|
35
|
+
This is the default mode when drawing operations are added.
|
|
36
|
+
"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
def remain(self) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Sets this layer to Remain mode - keeps previous content unchanged.
|
|
42
|
+
|
|
43
|
+
No operations are sent for this layer, saving bandwidth.
|
|
44
|
+
"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
def clear(self) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Sets this layer to Clear mode - clears to transparent with no redraw.
|
|
50
|
+
|
|
51
|
+
Use when you want to hide a layer without drawing new content.
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
# ============== Context State - Styling ==============
|
|
56
|
+
|
|
57
|
+
def set_stroke(self, color: RgbColor) -> None:
|
|
58
|
+
"""Sets the stroke color for subsequent draw operations."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def set_fill(self, color: RgbColor) -> None:
|
|
62
|
+
"""Sets the fill color for subsequent draw operations."""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
def set_thickness(self, width: int) -> None:
|
|
66
|
+
"""Sets the stroke thickness in pixels."""
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
def set_font_size(self, size: int) -> None:
|
|
70
|
+
"""Sets the font size in pixels."""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
def set_font_color(self, color: RgbColor) -> None:
|
|
74
|
+
"""Sets the font color for text operations."""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
# ============== Context State - Transforms ==============
|
|
78
|
+
|
|
79
|
+
def translate(self, dx: float, dy: float) -> None:
|
|
80
|
+
"""Sets the translation offset for subsequent draw operations."""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
def rotate(self, degrees: float) -> None:
|
|
84
|
+
"""Sets the rotation in degrees for subsequent draw operations."""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
def scale(self, sx: float, sy: float) -> None:
|
|
88
|
+
"""Sets the scale factors for subsequent draw operations."""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def skew(self, kx: float, ky: float) -> None:
|
|
92
|
+
"""Sets the skew factors for subsequent draw operations."""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
def set_matrix(
|
|
96
|
+
self,
|
|
97
|
+
scale_x: float,
|
|
98
|
+
skew_x: float,
|
|
99
|
+
trans_x: float,
|
|
100
|
+
skew_y: float,
|
|
101
|
+
scale_y: float,
|
|
102
|
+
trans_y: float,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Sets a full transformation matrix for subsequent draw operations.
|
|
106
|
+
|
|
107
|
+
Takes precedence over individual transform properties.
|
|
108
|
+
|
|
109
|
+
Matrix layout matches SKMatrix:
|
|
110
|
+
| ScaleX SkewX TransX |
|
|
111
|
+
| SkewY ScaleY TransY |
|
|
112
|
+
"""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
# ============== Context Stack ==============
|
|
116
|
+
|
|
117
|
+
def save(self) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Pushes the current context state onto a stack.
|
|
120
|
+
|
|
121
|
+
Use with restore() for hierarchical transforms.
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def restore(self) -> None:
|
|
126
|
+
"""Pops and restores the most recently saved context state."""
|
|
127
|
+
...
|
|
128
|
+
|
|
129
|
+
def reset_context(self) -> None:
|
|
130
|
+
"""Resets the context to default values (black stroke, identity transform)."""
|
|
131
|
+
...
|
|
132
|
+
|
|
133
|
+
# ============== Draw Operations ==============
|
|
134
|
+
|
|
135
|
+
def draw_polygon(self, points: Sequence[Tuple[float, float]]) -> None:
|
|
136
|
+
"""Draws a polygon using the current context state."""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
def draw_text(self, text: str, x: int, y: int) -> None:
|
|
140
|
+
"""Draws text at the specified position using the current context state."""
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
def draw_circle(self, center_x: int, center_y: int, radius: int) -> None:
|
|
144
|
+
"""Draws a circle using the current context state."""
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
def draw_rectangle(self, x: int, y: int, width: int, height: int) -> None:
|
|
148
|
+
"""Draws a rectangle using the current context state."""
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
def draw_line(self, x1: int, y1: int, x2: int, y2: int) -> None:
|
|
152
|
+
"""Draws a line using the current context state."""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
def draw_jpeg(self, jpeg_data: bytes, x: int, y: int, width: int, height: int) -> None:
|
|
156
|
+
"""Draws a JPEG image at the specified position and size."""
|
|
157
|
+
...
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Protocol V2 enums and constants for VectorGraphics encoding.
|
|
3
|
+
|
|
4
|
+
Matches C# ProtocolV2 from BlazorBlaze.VectorGraphics.Protocol.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from enum import IntEnum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FrameType(IntEnum):
|
|
13
|
+
"""
|
|
14
|
+
Frame type for layer updates.
|
|
15
|
+
|
|
16
|
+
Matches C# FrameType enum.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
MASTER = 0x00 # Clear and redraw with operations
|
|
20
|
+
REMAIN = 0x01 # Keep previous content unchanged
|
|
21
|
+
CLEAR = 0x02 # Clear to transparent with no redraw
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OpType(IntEnum):
|
|
25
|
+
"""
|
|
26
|
+
Operation type codes.
|
|
27
|
+
|
|
28
|
+
Matches C# OpType enum.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# Draw operations (0x01-0x0F)
|
|
32
|
+
DRAW_POLYGON = 0x01
|
|
33
|
+
DRAW_TEXT = 0x02
|
|
34
|
+
DRAW_CIRCLE = 0x03
|
|
35
|
+
DRAW_RECT = 0x04
|
|
36
|
+
DRAW_LINE = 0x05
|
|
37
|
+
DRAW_JPEG = 0x07
|
|
38
|
+
|
|
39
|
+
# Context operations (0x10-0x1F)
|
|
40
|
+
SET_CONTEXT = 0x10
|
|
41
|
+
SAVE_CONTEXT = 0x11
|
|
42
|
+
RESTORE_CONTEXT = 0x12
|
|
43
|
+
RESET_CONTEXT = 0x13
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PropertyId(IntEnum):
|
|
47
|
+
"""
|
|
48
|
+
Property IDs for SetContext operation.
|
|
49
|
+
|
|
50
|
+
Matches C# PropertyId enum.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# Styling properties (0x01-0x0F)
|
|
54
|
+
STROKE = 0x01
|
|
55
|
+
FILL = 0x02
|
|
56
|
+
THICKNESS = 0x03
|
|
57
|
+
FONT_SIZE = 0x04
|
|
58
|
+
FONT_COLOR = 0x05
|
|
59
|
+
|
|
60
|
+
# Transform properties (0x10-0x1F)
|
|
61
|
+
OFFSET = 0x10
|
|
62
|
+
ROTATION = 0x11
|
|
63
|
+
SCALE = 0x12
|
|
64
|
+
SKEW = 0x13
|
|
65
|
+
|
|
66
|
+
# Matrix (0x20)
|
|
67
|
+
MATRIX = 0x20
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Protocol constants
|
|
71
|
+
END_MARKER_BYTE1: int = 0xFF
|
|
72
|
+
END_MARKER_BYTE2: int = 0xFF
|