diff --git a/data/main_fields.yaml b/data/main_fields.yaml index 7c2a477..2bce216 100644 --- a/data/main_fields.yaml +++ b/data/main_fields.yaml @@ -19,7 +19,7 @@ type: uuid description: - Universally unique identifier of the material. - - If not specified, can be deduced from `brand_uuid` + `material_name`. + - If not specified, can be deduced from `brand_uuid` + `material_name` + `color_name`. - See _UUID_ section for more details. - key: 3 @@ -81,16 +81,20 @@ - Coarse classification of the material. - Useful for determining default parameters for preheat an such that are not explicitly specified in the data. - If the material does not match any of the proposed material types, can be left unspecified. + - Also see Material naming guidelines. - key: 10 name: material_name type: string max_length: 31 - example: PC Blend Carbon Fiber Black + example: PC Blend Carbon Fiber required: recommended description: - Brand-specific material display string/identifier. - - In the UI, brand_name + material_name should be displayed together, for example "Prusament PLA Galaxy Black". + - In the UI, `brand_name` + `material_name` + `color_name` should be displayed together, for example "Prusament PLA Carbon Fiber Black". + - MAY contain color name as well (for example `PC Blend Carbon Fiber Black`), but it is recommended to store the color name separate in the `color_name` field. + - Defaults to `material_abbreviation` if not present. + - Also see Material naming guidelines. - key: 52 name: material_abbreviation @@ -183,6 +187,16 @@ unit: g description: Weight of the empty container. +- key: 57 + name: color_name + type: string + max_length: 32 + required: recommended + example: Galaxy Black + description: + - Display/brand/marketing name of the material color. + - Also see Material naming guidelines. + - key: 19 name: primary_color type: color_rgba diff --git a/docs_src/nfc_data_format.md b/docs_src/nfc_data_format.md index e6b0d33..993e688 100644 --- a/docs_src/nfc_data_format.md +++ b/docs_src/nfc_data_format.md @@ -109,7 +109,7 @@ If a brand decides to change name but wants to keep the original `brand_uuid` th 1. `brand_name = Pepament` (present in the data), `brand_uuid = ae5ff34e-298e-50c9-8f77-92a97fb30b0` (present in the data) -### 3.2.1 UUID derivation algorithm +#### 3.2.1 UUID derivation algorithm UUIDs are derived from the brand-specific IDs using UUIDv5 with the `SHA1` hash, as specified in [RFC 4122, section 4.3](https://datatracker.ietf.org/doc/html/rfc4122#section-4.3), according to the following table. 1. UUIDs are hashed in the binary form. 1. Strings are encoded as UTF-8. @@ -117,11 +117,13 @@ UUIDs are derived from the brand-specific IDs using UUIDv5 with the `SHA1` hash, 1. `+` represents binary concatenation. 1. NFC tag UID is represented as a bytestream with the MSB being the first byte in the bytestream. * **Important:** Various apps/readers report these UIDs in various byte orders, and sometimes as hex strings instead of bytestreams. For NFCV, the UID MUST be a 8 bytes long bytestream with `0xE0` as the **first** byte (SLIX2 then follows with `0x04, 0x01`). +1. If `material_name` is not present, it defaults to `material_abbreviation`, which in turn defaults to abbreviation of `material_type`. +1. The space between `material_name` and `color_name` SHALL be omitted if `color_name` is empty. | UID | Derviation formula | Namespace (`N`) | | --- | --- | --- | | `brand_uuid` | `N + brand_name` | `5269dfb7-1559-440a-85be-aba5f3eff2d2` | -| `material_uuid` | `N + brand_uuid + material_name` | `616fc86d-7d99-4953-96c7-46d2836b9be9` | +| `material_uuid` | `N + brand_uuid + material_name + [" " + color_name]` | `616fc86d-7d99-4953-96c7-46d2836b9be9` | | `package_uuid` | `N + brand_uuid + gtin` | `6f7d485e-db8d-4979-904e-a231cd6602b2` | | `instance_uuid` | `N + nfc_tag_uid` | `31062f81-b5bd-4f86-a5f8-46367e841508` | @@ -143,6 +145,12 @@ material_name = "PLA Prusa Galaxy Black" material_uuid = generate_uuid(material_namespace, brand_uuid.bytes, material_name.encode("utf-8")) print(f"material_uuid = {material_uuid}") +material_namespace = "616fc86d-7d99-4953-96c7-46d2836b9be9" +material_name = "PLA" +color_name = "Prusa Galaxy Black" +material_uuid = generate_uuid(material_namespace, brand_uuid.bytes, material_name.encode("utf-8"), " ".encode("utf-8"), color_name.encode("utf-8")) +print(f"material_uuid = {material_uuid}") + material_package_namespace = "6f7d485e-db8d-4979-904e-a231cd6602b2" gtin = "1234" material_package_uuid = generate_uuid(material_package_namespace, brand_uuid.bytes, gtin.encode("utf-8")) @@ -154,6 +162,28 @@ material_package_instance_uuid = generate_uuid(material_package_instance_namespa print(f"material_package_instance_uuid = {material_package_instance_uuid}") {% endpython %} +### 3.3 Material naming guidelines +The full material name is defined as `brand_name + " " + material_name + " " + color_name`. For some materials, it can be difficult to determine how to split the full name to the individual fields. It is recommended to follow these guidelines: +1. Material "families" (such as PolyLite or PolySonic from Polymaker) SHOULD be part of `material_name`. One company SHOULD generally have a single `brand_name` entry. + 1. __Note: This is to have a single `brand_uuid` for the brand__ +1. `material_name` SHOULD contain terms that affect primarily material physical properties, such as "high speed", "carbon fiber", "recycled" and so on. + 1. Case study: If the material name contains the "carbon fiber" term to indicate visual imitation without actually containing carbon fibers (should also be reflected by not having the `contains_carbon_fiber` tag), the term SHOULD be part of the `color_name` instead. +1. `color_name` SHOULD contain terms that affect primarily material visual properties, such as "matte", "silk", "glow", "rainbow" and individual color names. +1. If the separation between `material_name` and `color_name` is unclear or if the separation would break product name word order, `color_name` MAY be omitted and all terms (excluding brand name) MAY be stored in `material_name`. +1. Please note that the defaulting mechanics of `material_name` and `material_abbreviation` fields apply for both material name and UUID derivation. + +#### 3.3.1 Naming examples +| `brand_name` | `material_name` | `color_name` | `abbreviation` | Notes | +| - | - | - | - | +| Prusament | PLA | Prusa Galaxy Black | PLA | | +| Prusament | PC Blend Cabron Fiber | Black | PCCF | | +| Prusament | rPLA | Algae Pigment | rPLA | | +| Prusament | PETG V0 | Natural | PETGV0 | | +| Prusament | PLA Recycled | | rPLA | It is unclear whether "Recycled" stands for color or material name → defaulting everything to `material_name` | +| Polymaker | PolyLite PLA | White | PLA | +| Polymaker | PolySonic PLA | White | PLA | +| Polymaker | PolyLite Luminous PLA Rainbow | | PLA | "Luminous" should technically be a part of `color_name`, defaulting everything to `material_name` to preserve word order. | + ## 4. Meta section The meta section allows defining of region offsets (within the NDEF payload) and sizes. diff --git a/tests/encode_decode/01_info.yaml b/tests/encode_decode/01_info.yaml index 1bdbcae..73eec40 100644 --- a/tests/encode_decode/01_info.yaml +++ b/tests/encode_decode/01_info.yaml @@ -61,10 +61,15 @@ raw_data: aux: a000000000000000000000000000000000000000000000000000000000000000000000 uri: https://3dtag.org/s/334c54f088 opt_check: - warnings: [] + warnings: + - Missing recommended field 'color_name' errors: [] notes: [] - uuids: + deductions: + material_abbreviation: PLA + material_name: PLA Prusa Galaxy Black + extended_material_name: PLA Prusa Galaxy Black + full_material_name: Prusament PLA Prusa Galaxy Black brand_uuid: ae5ff34e-298e-50c9-8f77-92a97fb30b09 material_uuid: 1aaca54a-431f-5601-adf5-85dd018f487f package_uuid: 6e0aece2-1daf-5f2a-ba20-697968ec7d14 diff --git a/tests/encode_decode/02_info.yaml b/tests/encode_decode/02_info.yaml index e0184a8..4712405 100644 --- a/tests/encode_decode/02_info.yaml +++ b/tests/encode_decode/02_info.yaml @@ -57,10 +57,15 @@ raw_data: aux: a000000000000000000000000000000000000000000000000000000000000000000000 uri: https://3dtag.org/s/7ab2acb509 opt_check: - warnings: [] + warnings: + - Missing recommended field 'color_name' errors: [] notes: [] - uuids: + deductions: + material_abbreviation: PETG + material_name: PETG Jet Black + extended_material_name: PETG Jet Black + full_material_name: Prusament PETG Jet Black brand_uuid: ae5ff34e-298e-50c9-8f77-92a97fb30b09 material_uuid: 1378e978-35ed-534c-9dfa-a65525bf8649 package_uuid: 6f957b59-9725-5068-9102-15bb77807534 diff --git a/tests/encode_decode/03_info.yaml b/tests/encode_decode/03_info.yaml index 340f2c4..7f46223 100644 --- a/tests/encode_decode/03_info.yaml +++ b/tests/encode_decode/03_info.yaml @@ -58,6 +58,8 @@ uri: null opt_check: warnings: - Missing recommended field 'brand_name' + - Missing recommended field 'color_name' + - Failed to deduce 'full_material_name' errors: - 'Fields preheat_temperature (300), min_print_temperature (240): a <= b' - 'Fields preheat_temperature (300), max_print_temperature (210): a <= b' @@ -70,7 +72,11 @@ opt_check: - Failed to deduce material_uuid - Failed to deduce package_uuid notes: [] - uuids: + deductions: + material_abbreviation: PETG + material_name: PETG Jet Black + extended_material_name: PETG Jet Black + full_material_name: null brand_uuid: null material_uuid: null package_uuid: null diff --git a/tests/encode_decode/04_info.yaml b/tests/encode_decode/04_info.yaml index 2ed0e20..2746f94 100644 --- a/tests/encode_decode/04_info.yaml +++ b/tests/encode_decode/04_info.yaml @@ -62,11 +62,16 @@ uri: https://www.3dxtech.com/ opt_check: warnings: - Missing recommended field 'gtin' + - Missing recommended field 'color_name' - Missing recommended field 'preheat_temperature' errors: - Failed to deduce package_uuid notes: [] - uuids: + deductions: + material_abbreviation: PEKK-CF + material_name: CarbonX PEKK-A+CF15 Black + extended_material_name: CarbonX PEKK-A+CF15 Black + full_material_name: 3DXTech CarbonX PEKK-A+CF15 Black brand_uuid: 0d616a90-9d18-567b-92f9-ce471171f898 material_uuid: 09d4c3e3-f13c-5867-bee3-88eeccb48c6f package_uuid: null diff --git a/tests/encode_decode/05_data.bin b/tests/encode_decode/05_data.bin new file mode 100644 index 0000000..8818827 Binary files /dev/null and b/tests/encode_decode/05_data.bin differ diff --git a/tests/encode_decode/05_info.yaml b/tests/encode_decode/05_info.yaml new file mode 100644 index 0000000..9d98d33 --- /dev/null +++ b/tests/encode_decode/05_info.yaml @@ -0,0 +1,76 @@ +regions: + meta: + payload_offset: 0 + absolute_offset: 66 + size: 4 + used_size: 4 + main: + payload_offset: 4 + absolute_offset: 70 + size: 206 + used_size: 145 + aux: + payload_offset: 210 + absolute_offset: 276 + size: 35 + used_size: 1 +root: + data_size: 312 + payload_size: 245 + overhead: 67 + payload_used_size: 150 + total_used_size: 217 +data: + meta: + aux_region_offset: 210 + main: + gtin: 8594173675001 + brand_specific_instance_id: 334c54f088 + material_class: FFF + material_type: PLA + brand_name: Prusament + manufactured_date: 1758709719 + nominal_netto_full_weight: 1000 + actual_netto_full_weight: 1012 + empty_container_weight: 280 + primary_color: + hex: 3d3e3d + tags: + - glitter + density: 1.24 + min_print_temperature: 205 + max_print_temperature: 225 + preheat_temperature: 170 + min_bed_temperature: 40 + max_bed_temperature: 60 + min_chamber_temperature: 18 + max_chamber_temperature: 40 + chamber_temperature: 20 + container_width: 64 + container_outer_diameter: 200 + container_inner_diameter: 100 + container_hole_diameter: 52 + certifications: + - ul_2818 + - ul_94_v0 + color_name: Prusa Galaxy Black + aux: {} +raw_data: + meta: a10218d2 + main: bf041b000007d0fcab45f9056a33333463353466303838080009000b6950727573616d656e740e1a68d3c7d7101903e8111903f41219011813433d3e3d181c9f17ff181df93cf6182218cd182318e1182418aa182518281826183c18271218281828182914182a1840182b18c8182c1864182d183418389f0001ff18397250727573612047616c61787920426c61636bff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + aux: a000000000000000000000000000000000000000000000000000000000000000000000 +uri: https://3dtag.org/s/334c54f088 +opt_check: + warnings: + - Missing recommended field 'material_name' + errors: [] + notes: [] + deductions: + material_abbreviation: PLA + material_name: PLA + extended_material_name: PLA Prusa Galaxy Black + full_material_name: Prusament PLA Prusa Galaxy Black + brand_uuid: ae5ff34e-298e-50c9-8f77-92a97fb30b09 + material_uuid: 1aaca54a-431f-5601-adf5-85dd018f487f + package_uuid: 6e0aece2-1daf-5f2a-ba20-697968ec7d14 + instance_uuid: bf63e92d-9ca5-53d7-9fab-ffdd0240c585 diff --git a/tests/encode_decode/05_input.yaml b/tests/encode_decode/05_input.yaml new file mode 100644 index 0000000..da5241a --- /dev/null +++ b/tests/encode_decode/05_input.yaml @@ -0,0 +1,42 @@ +test_config: + uri: https://3dtag.org/s/334c54f088 + tag_uid: E0040108662F6FBC + extra_required_fields: null +data: + main: + # Material information + material_class: FFF + material_type: PLA + brand_name: Prusament + color_name: Prusa Galaxy Black + primary_color: + hex: 3D3E3D + tags: [glitter] + certifications: [ul_2818, ul_94_v0] + density: 1.24 + + # Package-spcific fields + gtin: 8594173675001 + nominal_netto_full_weight: 1000 + + # Instance-specific fields + brand_specific_instance_id: "334c54f088" + manufactured_date: 1758709719 + actual_netto_full_weight: 1012 + + # Printing parameters + min_print_temperature: 205 + max_print_temperature: 225 + min_bed_temperature: 40 + max_bed_temperature: 60 + preheat_temperature: 170 + chamber_temperature: 20 + min_chamber_temperature: 18 + max_chamber_temperature: 40 + + # Container info + empty_container_weight: 280 + container_outer_diameter: 200 + container_inner_diameter: 100 + container_hole_diameter: 52 + container_width: 64 diff --git a/utils/opt_check.py b/utils/opt_check.py index f34e085..29e2488 100644 --- a/utils/opt_check.py +++ b/utils/opt_check.py @@ -14,7 +14,7 @@ def opt_check(rec: Record, tag_uid: bytes = None): warnings = list() errors = list() notes = list() - uuids = dict() + deductions = dict() main_data = rec.main_region.read() @@ -83,6 +83,54 @@ def check_relation(fields: list[str], func, error=None): check_relation(["container_hole_diameter", "container_inner_diameter", "container_outer_diameter"], lambda a, b: a <= b) + # Deduce material name + if deduced_abbreviation := main_data.get("material_abbreviation"): + pass + + elif deduced_abbreviation := main_data.get("material_type"): + pass + + else: + warnings.append("Failed to deduce `material_abbreviation`") + + deductions["material_abbreviation"] = deduced_abbreviation + + if deduced_material_name := main_data.get("material_name"): + pass + + elif deduced_material_name := deduced_abbreviation: + pass + + else: + warnings.append("Failed to deduce `material_name`") + + deductions["material_name"] = deduced_material_name + + def deduce_extended_material_name(): + result = [] + + if deduced_material_name: + result.append(deduced_material_name) + else: + warnings.append("Failed to deduce 'extended_material_name': missing 'material_name'") + return None + + if color_name := main_data.get("color_name"): + result.append(color_name) + + return " ".join(result) + + extended_material_name = deduce_extended_material_name() + deductions["extended_material_name"] = extended_material_name + + if (brand_name := main_data.get("brand_name")) and extended_material_name: + full_material_name = f"{brand_name} {extended_material_name}" + else: + full_material_name = None + warnings.append("Failed to deduce 'full_material_name'") + + deductions["full_material_name"] = full_material_name + # Check and deduce UUIDs def generate_uuid(namespace, *args): return uuid.uuid5(uuid.UUID(namespace), b"".join(args)) @@ -106,7 +154,7 @@ def deduce_uuid(field, generated_uuid, report_deduce_fail: bool = True): if generated_uuid and result != generated_uuid: notes.append(f"{field} ({result}) differes from auto-generated {generate_uuid}") - uuids[field] = str(result) if result else None + deductions[field] = str(result) if result else None if brand_name := main_data.get("brand_name"): brand_generated_uuid = generate_uuid("5269dfb7-1559-440a-85be-aba5f3eff2d2", brand_name.encode("utf-8")) @@ -115,14 +163,14 @@ def deduce_uuid(field, generated_uuid, report_deduce_fail: bool = True): deduce_uuid("brand_uuid", brand_generated_uuid) - if (brand_uuid := uuids["brand_uuid"]) and (material_name := main_data.get("material_name")): - material_generated_uuid = generate_uuid("616fc86d-7d99-4953-96c7-46d2836b9be9", uuid.UUID(brand_uuid).bytes, material_name.encode("utf-8")) + if (brand_uuid := deductions["brand_uuid"]) and extended_material_name: + material_generated_uuid = generate_uuid("616fc86d-7d99-4953-96c7-46d2836b9be9", uuid.UUID(brand_uuid).bytes, extended_material_name.encode("utf-8")) else: material_generated_uuid = None deduce_uuid("material_uuid", material_generated_uuid) - if (brand_uuid := uuids["brand_uuid"]) and (gtin := main_data.get("gtin")): + if (brand_uuid := deductions["brand_uuid"]) and (gtin := main_data.get("gtin")): package_generated_uuid = generate_uuid("6f7d485e-db8d-4979-904e-a231cd6602b2", uuid.UUID(brand_uuid).bytes, str(gtin).encode("utf-8")) else: package_generated_uuid = None @@ -132,7 +180,7 @@ def deduce_uuid(field, generated_uuid, report_deduce_fail: bool = True): if tag_uid and tag_uid[0] != 0xE0: warnings.append(f"Tag UID {tag_uid.hex()} doesn't start with 0xE0") - if (brand_uuid := uuids["brand_uuid"]) and tag_uid: + if (brand_uuid := deductions["brand_uuid"]) and tag_uid: assert tag_uid[0] == 0xE0, "Make sure tag_uid is in the correct byte order" instance_generated_uuid = generate_uuid("31062f81-b5bd-4f86-a5f8-46367e841508", tag_uid) else: @@ -144,7 +192,7 @@ def deduce_uuid(field, generated_uuid, report_deduce_fail: bool = True): "warnings": warnings, "errors": errors, "notes": notes, - "uuids": uuids, + "deductions": deductions, }