Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion data/main_fields.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,28 @@
- The alpha channel can be left out, in which case the data should have 3 bytes instead of 4 and the color will be considered fully opaque.
- If a material doesn't have a single primary color (for example rainbow or coextruded filaments), this field can be null.

- key: 59
name: primary_color_lab
type: color_lab
unit: '[L*, a*, b*]'
example: '[53.24, 111.12, -27.3]'
description:
- "Color of a material in the device-independent CIE L*a*b* (CIELAB 1976) color space with reference white D65/2\xB0."
- If present, the value MUST be obtained by physical spectrometry measurement; it MUST NOT be approximated (for example from RGB).
- "`L*` is bound to [0, 100], `a*` and `b*` values are dimensionless and are typically between \xB1127, but can theoretically get in the \xB1150 range."

- key: 60
name: primary_color_ral
type: string
unit: RAL code
max_length: 16
example: 270 30 20
description:
- RAL color identifier, without the "RAL" prefix.
- The value MUST correspond exactly to an official identifier (see https://www.ral-farben.de/en/all-ral-colours).
- If present, the physical material MUST match the referenced RAL swatch; it MUST NOT be approximated (for example from RGB/LAB).
- 'Examples of valid values: `3020`, `9005`, `1023`, `7016`, `270 30 20`, `190 50 35`, `530-1`, `850-M`, `P1 3020`.'

- key: 20
name: secondary_color_0
type: color_rgba
Expand Down Expand Up @@ -485,4 +507,4 @@
description:
- Recommended drying time (at `drying_temperature`).

# First unused key: 59
# First unused key: 61
5 changes: 3 additions & 2 deletions docs_src/nfc_data_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@
1. `enum` fields are encoded as an integer, according to the enum field mapping
1. `enum_array` fields are encoded as CBOR arrays of integers, according to the field mapping
1. `timestamp` fields are encoded as UNIX timestamp integers
1. `bytes` and `uuid` types are encoded as CBOR byte string (type 2)
1. `color_rgba` fields are encoded as a CBOR byte string (type 2) with 3 to 4 bytes representing `[R, G, B]` or `[R, G, B, A]` values
1. `number` types can be encoded as either unsigned integers (type 0), signed integers (type 1), half floats or floats
1. `bytes` and `uuid` types are encoded as CBOR byte string (type 2)
1. `string` types are encoded as CBOR text string (type 3, UTF-8 is enforced by the CBOR specification)
1. The `X` in the `string:X` or `bytes:X` notation defines maximum permissible length of the data in bytes.
1. `color_rgba` fields are encoded as a CBOR byte string (type 2) with 3 to 4 bytes representing `[R, G, B]` or `[R, G, B, A]` values
1. `color_lab` fields are encoded as a CBOR array of 3 `number` type elements

### 3.2 UUIDs
Some entities referenced in the data (see [Terminology](terminology.md)) can be identified by a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier). The UUID MAY be explicitly specified through a `XX_uuid` field, however that might not be desirable due to space constraints. As an alternative, the following algorithm defines a way to derive UUIDs from other fields.
Expand Down
Binary file modified tests/encode_decode/01_data.bin
Binary file not shown.
13 changes: 9 additions & 4 deletions tests/encode_decode/01_info.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ regions:
payload_offset: 4
absolute_offset: 70
size: 206
used_size: 149
used_size: 176
aux:
payload_offset: 210
absolute_offset: 276
Expand All @@ -21,8 +21,8 @@ root:
data_size: 312
payload_size: 245
overhead: 67
payload_used_size: 154
total_used_size: 221
payload_used_size: 181
total_used_size: 248
data:
meta:
aux_region_offset: 210
Expand Down Expand Up @@ -56,10 +56,15 @@ data:
certifications:
- ul_2818
- ul_94_v0
primary_color_lab:
- 50
- 11.3
- 129.3
primary_color_ral: 270 30 20
aux: {}
raw_data:
meta: a10218d2
main: bf041b000007d0fcab45f9056a33333463353466303838080009000a76504c412050727573612047616c61787920426c61636b0b6950727573616d656e740e1a68d3c7d7101903e8111903f41219011813443d3e3dff181c9f17ff181df93cf6182218cd182318e1182418aa182518281826183c18271218281828182914182a1840182b18c8182c1864182d183418389f0001ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
main: bf041b000007d0fcab45f9056a33333463353466303838080009000a76504c412050727573612047616c61787920426c61636b0b6950727573616d656e740e1a68d3c7d7101903e8111903f41219011813443d3e3dff181c9f17ff181df93cf6182218cd182318e1182418aa182518281826183c18271218281828182914182a1840182b18c8182c1864182d183418389f0001ff183b831832fa4134cccdfa43014ccd183c69323730203330203230ff000000000000000000000000000000000000000000000000000000000000
aux: a000000000000000000000000000000000000000000000000000000000000000000000
uri: https://3dtag.org/s/334c54f088
validate:
Expand Down
2 changes: 2 additions & 0 deletions tests/encode_decode/01_input.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ data:
brand_name: Prusament
material_name: PLA Prusa Galaxy Black
primary_color: "#3d3e3dff"
primary_color_lab: [50, 11.3, 129.3]
primary_color_ral: 270 30 20
tags: [glitter]
certifications: [ul_2818, ul_94_v0]
density: 1.24
Expand Down
58 changes: 41 additions & 17 deletions utils/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,25 @@ def __init__(self, num: float):
if num.is_integer():
self.value = int(num)

elif abs(num - numpy.float16(num)) < CompactFloat.required_precision:
elif abs(num - float(numpy.float16(num))) < CompactFloat.required_precision:
self.value = float(numpy.float16(num))

elif abs(num - numpy.float32(num)) < CompactFloat.required_precision:
elif abs(num - float(numpy.float32(num))) < CompactFloat.required_precision:
self.value = float(numpy.float32(num))

else:
self.value = num

@staticmethod
def decode(data):
num = float(data)
return int(num) if num.is_integer() else round(num, CompactFloat.decimal_precision)

def encode_cbor(self, encoder: cbor2.CBOREncoder):
# Always encode floats canonically
# Noncanonically, floats would always be encoded in 8 B, which is a lot of wasted space
cbor2.CBOREncoder(encoder.fp, canonical=True).encode(self.value)


# Represent a raw CBOR data that are to be encoded verbatim
class RawCBORData:
Expand All @@ -49,6 +59,9 @@ class RawCBORData:
def __init__(self, data: bytes):
self.data = data

def encode_cbor(self, encoder: cbor2.CBOREncoder):
encoder.fp.write(self.data)


class Field:
key: int
Expand Down Expand Up @@ -81,8 +94,7 @@ def encode(self, data):

class NumberField(Field):
def decode(self, data):
num = float(data)
return int(num) if num.is_integer() else round(num, CompactFloat.decimal_precision)
return CompactFloat.decode(data)

def encode(self, data):
return CompactFloat(data)
Expand Down Expand Up @@ -185,6 +197,29 @@ def encode(self, data):
return bytes.fromhex(m.group(1))


class LABCborData:
data: list

def __init__(self, data: list):
self.data = data

def encode_cbor(self, encoder: cbor2.CBOREncoder):
# Encode with definite container, it's smaller and the record is fixed size
cbor2.CBOREncoder(encoder.fp, canonical=True, indefinite_containers=False, default=encoder.default).encode(self.data)


class ColorLABField(Field):
def decode(self, data):
assert type(data) is list
assert len(data) == 3
return [CompactFloat.decode(x) for x in data]

def encode(self, data):
assert type(data) is list
assert len(data) == 3
return LABCborData([CompactFloat(x) for x in data])


class UUIDField(Field):
def decode(self, data):
return str(uuid.UUID(bytes=data))
Expand All @@ -202,6 +237,7 @@ def encode(self, data):
"enum_array": EnumArrayField,
"timestamp": IntField,
"color_rgba": ColorRGBAField,
"color_lab": ColorLABField,
"uuid": UUIDField,
}

Expand Down Expand Up @@ -304,24 +340,12 @@ def update(
for key, value in update_unknown_fields.items():
result[RawCBORData(bytes.fromhex(key))] = RawCBORData(bytes.fromhex(value))

def default_enc(enc: cbor2.CBOREncoder, data: typing.Any):
if isinstance(data, CompactFloat):
# Always encode floats canonically
# Noncanonically, floats would always be encoded in 8 B, which is a lot of wasted space
cbor2.CBOREncoder(enc.fp, canonical=True).encode(data.value)

elif isinstance(data, RawCBORData):
enc.fp.write(data.data)

else:
raise RuntimeError(f"Unsupported type {type(data)} to encode")

data_io = io.BytesIO()
encoder = cbor2.CBOREncoder(
data_io,
canonical=config.canonical,
indefinite_containers=config.indefinite_containers,
default=default_enc,
default=lambda enc, data: data.encode_cbor(enc),
)

encoder.encode(result)
Expand Down
23 changes: 23 additions & 0 deletions utils/schema/field_types.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,29 @@
"type": "string",
"description": "RGB(A) color in a standard hex notation '#RRGGBB(AA)'",
"pattern": "^#[0-9a-f]{6}([0-9a-f]{2})?$"
},
"color_lab": {
"type": "array",
"prefixItems": [
{
"type": "number",
"minimum": 0,
"maximum": 100
},
{
"type": "number",
"minimum": -150,
"maximum": 150
},
{
"type": "number",
"minimum": -150,
"maximum": 150
}
],
"items": false,
"minItems": 3,
"maxItems": 3
}
}
}
6 changes: 6 additions & 0 deletions utils/schema/fields.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
"primary_color": {
"$ref": "field_types.schema.json#/definitions/color_rgba"
},
"primary_color_lab": {
"$ref": "field_types.schema.json#/definitions/color_lab"
},
"primary_color_ral": {
"$ref": "field_types.schema.json#/definitions/string"
},
"secondary_color_0": {
"$ref": "field_types.schema.json#/definitions/color_rgba"
},
Expand Down
Loading