diff --git a/.github/workflows/gitlab-trigger.yml b/.github/workflows/gitlab-trigger.yml deleted file mode 100644 index f7cc6e9..0000000 --- a/.github/workflows/gitlab-trigger.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Trigger GitLab Pipeline - -on: - push: - branches: - - main - -jobs: - trigger-gitlab-deployment: - runs-on: ubuntu-latest - if: github.repository == 'prusa3d/OpenPrintTag' - steps: - - name: Send trigger to WebDev GitLab - run: | - curl -X POST --fail \ - -F token=${{ secrets.GITLAB_TRIGGER_TOKEN }} \ - -F ref=main \ - https://gitlab.com/api/v4/projects/68872123/trigger/pipeline diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7803ed3..eb5d412 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,16 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - exclude: docs_src/media/adhesive_spec.pdf - - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.5 - hooks: - - id: ruff-format +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: check-yaml + - id: check-added-large-files + exclude: docs_src/media/adhesive_spec.pdf + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.5 + hooks: + - id: ruff-format diff --git a/LICENSE b/LICENSE index 6939b50..c363069 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ -Copyright 2025 PRUSA RESEARCH A.S. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - +Copyright 2025 PRUSA RESEARCH A.S. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index cbd820a..caa4d36 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ -# OpenPrintTag -This repository contains specification, documentation and utility scripts for the [OpenPrintTag](https://openprinttag.org) format. - -**This is a "raw" repository, you can access the compiled documentation on [specs.openprinttag.org](https://specs.openprinttag.org)** - -(or use generate_docs.sh to generate a website into the docs folder) - -## Directory structure -* `data`: Machine-readable specification data (field & enum definitions, ...) -* `docs_src`: Source code for the [specs.openprinttag.org](https://specs.openprinttag.org) website -* `tests`: Tests -* `utils`: Reference implementation for the format in Python - -## Generating documentation -To generate documentation (to the `docs` directory), run: -```python -pip3 install -r requirements.txt -sh generate_docs.sh -``` - -Then, to view it, you can: -``` -cd docs -python3 -m http.server -``` -and open your browser on `127.0.0.1:8000` +# OpenPrintTag +This repository contains specification, documentation and utility scripts for the [OpenPrintTag](https://openprinttag.org) format. + +**This is a "raw" repository, you can access the compiled documentation on [specs.openprinttag.org](https://specs.openprinttag.org)** + +(or use generate_docs.sh to generate a website into the docs folder) + +## Directory structure +* `data`: Machine-readable specification data (field & enum definitions, ...) +* `docs_src`: Source code for the [specs.openprinttag.org](https://specs.openprinttag.org) website +* `tests`: Tests +* `utils`: Reference implementation for the format in Python + +## Generating documentation +To generate documentation (to the `docs` directory), run: +```python +pip3 install -r requirements.txt +sh generate_docs.sh +``` + +Then, to view it, you can: +``` +cd docs +python3 -m http.server +``` +and open your browser on `127.0.0.1:8000` diff --git a/data/main_fields.yaml b/data/main_fields.yaml index 910927f..d2cb8c4 100644 --- a/data/main_fields.yaml +++ b/data/main_fields.yaml @@ -1,509 +1,517 @@ -- key: 0 - name: instance_uuid - type: uuid - description: - - Unique identifier of the package instance. - - If not specified, can be deduced from `brand_uuid` + NFC tag UID. - - See _UUID_ section for more details. - -- key: 1 - name: package_uuid - type: uuid - description: - - Universally unique identifier of the package (product) - - If not specified, can be deduced from `brand_uuid` + `gtin`. - - See _UUID_ section for more details. - -- key: 2 - name: material_uuid - type: uuid - description: - - Universally unique identifier of the material. - - If not specified, can be deduced from `brand_uuid` + `material_name`. - - See _UUID_ section for more details. - -- key: 3 - name: brand_uuid - type: uuid - description: - - Universally unique identifier of the brand - - If not specified, can be deduced from the `brand_name` string. - - See _UUID_ section for more details. - -- key: 4 - name: gtin - type: number - required: recommended - description: Global Trade Item Number. - -- key: 5 - name: brand_specific_instance_id - type: string - max_length: 16 - description: - - Brand-specific identifier of the package instance. - - Not much use cases at this moment, possibly just for URL deduction - -- key: 6 - name: brand_specific_package_id - type: string - max_length: 16 - description: - - Brand-specific identifier of the package (product ID). - - Not much use cases at this moment, possibly just for URL deduction - -- key: 7 - name: brand_specific_material_id - type: string - max_length: 16 - description: - - Together with brand uniquely identifies each material. - - Not much use cases at this moment, possibly just for URL deduction. - -- key: 8 - name: material_class - type: enum - required: true - example: FFF - items_file: material_class_enum.yaml - display_name_field: description - -- key: 9 - name: material_type - type: enum - category: fff - items_file: material_type_enum.yaml - name_field: abbreviation - display_name_field: name - required: recommended - example: PC - description: - - 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. - -- key: 10 - name: material_name - type: string - max_length: 63 - example: PC Blend Carbon Fiber Black - 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". - -- key: 52 - name: material_abbreviation - type: string - max_length: 7 - example: PCCF - description: - - Abbreviation of the material name, for UI purposes (footers, dashboards, ...). - - If not present, the material inherits the abbreviation from the material type. - -- key: 11 - name: brand_name - type: string - max_length: 31 - required: recommended - description: Brand of the material. - example: Prusament - -- key: 12 - deprecated: true - -- key: 13 - name: write_protection - type: enum - items_file: write_protection_enum.yaml - description: - - Indicates whether the tag is write protected (everything except aux section, that one should be always writable). - - See the _Write protection_ section. - -- key: 14 - name: manufactured_date - type: timestamp - required: recommended - -- key: 55 - name: country_of_origin - type: string - max_length: 2 - description: Country the [MaterialPackageInstance](terminology) was produced in, encoded as a two-letter code according to [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). - -- key: 15 - name: expiration_date - type: timestamp - -- key: 16 - name: nominal_netto_full_weight - required: recommended - type: number - unit: g - example: 1000 - description: - - Nominal/advertised weight of the full package of the material, excluding the container. - - The actual netto weight of a specific package instance can slightly differ and is specified by `actual_netto_full_weight`. - -- key: 17 - name: actual_netto_full_weight - required: recommended - type: number - unit: g - example: 1012 - description: - - Actual weight of the full package of the material of this specific package instance, excluding the weight of the container. - - Can slightly differ from `nominal_netto_full_weight`. - - If not present, `nominal_netto_full_weight` SHALL be used as a fallback. - -- key: 53 - name: nominal_full_length - required: recommended - category: fff - type: number - unit: mm - example: 350000 - description: - - Nominal/advertised filament length of the full spool. - - The actual length of a specific package instance can slightly differ and is specified by `actual_full_length` - -- key: 54 - name: actual_full_length - required: recommended - category: fff - type: number - unit: mm - example: 351000 - description: - - Actual filament length of the full spool. - - Can slightly differ from `nominal_full_length`. - - If not present, `nominal_full_length` SHALL be used as a fallback. - -- key: 18 - name: empty_container_weight - required: recommended - type: number - unit: g - description: Weight of the empty container. - -- key: 19 - name: primary_color - type: color_rgba - required: recommended - example: "`\\xff\\x00\\x00\\x7f`" - description: - - Primary color of the material in the RGB(A) format, intended for UI purposes. - - 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 - description: - - One of secondary colors of the material. - - Data format is the same as for `primary_color`. - -- key: 21 - name: secondary_color_1 - type: color_rgba - description: See `secondary_color_0`. - -- key: 22 - name: secondary_color_2 - type: color_rgba - description: See `secondary_color_0`. - -- key: 23 - name: secondary_color_3 - type: color_rgba - description: See `secondary_color_0`. - -- key: 24 - name: secondary_color_4 - type: color_rgba - description: See `secondary_color_0`. - -- key: 25 - deprecated: true - -- key: 26 - deprecated: true - -- key: 27 - name: transmission_distance - type: number - example: 6.6 - unit: HueForge TD - description: - - Transmission Distance is a number representing material opacity. - - Value ranges from 0.1 (least transparent/most opaque) to 100 (most transparent/least opaque). - - See [Prusa TD values](https://help.prusa3d.com/article/hueforge-filament-transparency-values-and-hexcodes_762314) or [HueForge website](https://shop.thehueforge.com/blogs/news/what-is-hueforge). - -- key: 28 - name: tags - type: enum_array - max_length: 16 - items_file: tags_enum.yaml - example: "glitter + dual_color" - required: recommended - description: Properties of the material. Can have multiple tags at once. - -- key: 56 - name: certifications - type: enum_array - max_length: 8 - items_file: material_certifications_enum.yaml - example: "`ul_2818`" - description: Certifications the material has. - -- key: 29 - name: density - type: number - unit: g/cm³ (1 g/cm³ = 0.001 g/mm³ = 1000 kg/m³) - example: 1.24 - required: recommended - description: Density of the material. - -- key: 30 - name: filament_diameter - type: number - unit: mm - example: 2.75 - category: fff - description: - - Diameter of the filament, in mm. - - If not present, 1.75 mm is assumed. - -# Removed 18 - -# Removed 19 - -- key: 31 - name: hardness_shore_a - type: int - example: 95 - description: - - Hardness of the material on the Shore A hardness scale (suitable for softer materials). - - "**Note:** There is no 1:1 mapping between A and D scales, different materials can have different values on one scale even though they are the same on the other." - -- key: 32 - name: hardness_shore_d - type: int - example: 30 - description: - - Hardness of the material on the Shore D hardness scale (suitable for harder materials). - - "**Note:** There is no 1:1 mapping between A and D scales, different materials can have different values on one scale even though they are the same on the other." - -- key: 33 - name: min_nozzle_diameter - type: number - example: 0.4 - unit: mm - category: fff - description: - - Filaments can contain particles that would clog smaller nozzles. - - This field specifies minimum nozzle diameter recommended for printing this material. - -- key: 34 - name: min_print_temperature - type: int - unit: °C - example: 205 - required: recommended - category: fff - description: - - Minimum recommended nozzle temperature for printing. - - Also used for loading the filament to the nozzle. - -- key: 35 - name: max_print_temperature - type: int - unit: °C - example: 225 - required: recommended - category: fff - description: - - Maximum recommended nozzle temperature for printing. - - Also used for loading the filament to the nozzle. - -- key: 36 - name: preheat_temperature - type: int - unit: °C - example: 170 - category: fff - required: recommended - description: - - Recommended nozzle temperature for preheating/load cell bed leveling. - - Should be large enough for the material to get soft, but not low enough for it no to drip out of the nozzle. - -- key: 37 - name: min_bed_temperature - type: int - unit: °C - example: 60 - category: fff - required: recommended - description: - - Minimum recommended heatbed temperature. - -- key: 38 - name: max_bed_temperature - type: int - unit: °C - example: 60 - category: fff - required: recommended - description: - - Maximum recommended heatbed temperature. - -- key: 39 - name: min_chamber_temperature - type: int - unit: °C - example: 10 - category: fff - description: - - Minimum recommended temperature of the chamber. - -- key: 40 - name: max_chamber_temperature - type: int - unit: °C - example: 50 - category: fff - description: - - Maximum recommended temperature of the chamber. - -- key: 41 - name: chamber_temperature - type: int - unit: °C - example: 20 - category: fff - description: - - Ideal chamber temperature for printing. - -- key: 42 - name: container_width - type: int - unit: mm - example: 75 - category: fff - description: - - Width of the filament spool. Can be useful to know for spool holders, dry boxes and such. - -- key: 43 - name: container_outer_diameter - type: int - unit: mm - example: 200 - category: fff - description: - - Diameter of the spool. Can be useful to know for spool holders, dry boxes and such. - -- key: 44 - name: container_inner_diameter - type: int - unit: mm - example: 100 - category: fff - description: - - Diameter of the inner cylinder the filament is spooled once. - - Equals to the minimum diameter of the filament winding. - -- key: 45 - name: container_hole_diameter - type: int - unit: mm - example: 52 - category: fff - description: - - Diameter of the center hole of the spool. - -- key: 46 - name: viscosity_18c - type: number - unit: mPa·s - description: Viscosity of the material at 18 °C. - category: sla - -- key: 47 - name: viscosity_25c - type: number - unit: mPa·s - description: Viscosity of the material at 25 °C. - category: sla - example: 80 - -- key: 48 - name: viscosity_40c - type: number - unit: mPa·s - description: Viscosity of the material at 40 °C. - category: sla - -- key: 49 - name: viscosity_60c - type: number - unit: mPa·s - description: Viscosity of the material at 60 °C. - category: sla - -- key: 50 - name: container_volumetric_capacity - type: number - unit: ml (cm³) - category: sla - description: Maximum amount of material the container can hold. - -- key: 51 - name: cure_wavelength - type: int - unit: nm - example: 405 - category: sla - description: - - Wavelength of the light the material has been designed to be cured with. - -- key: 57 - name: drying_temperature - type: int - unit: °C - example: 45 - category: fff - description: - - Recommended ambient temperature for drying. - -- key: 58 - name: drying_time - type: int - unit: min - example: 480 - category: fff - description: - - Recommended drying time (at `drying_temperature`). - -# First unused key: 61 +- key: 0 + name: instance_uuid + type: uuid + description: + - Unique identifier of the package instance. + - If not specified, can be deduced from `brand_uuid` + NFC tag UID. + - See _UUID_ section for more details. + +- key: 1 + name: package_uuid + type: uuid + description: + - Universally unique identifier of the package (product) + - If not specified, can be deduced from `brand_uuid` + `gtin`. + - See _UUID_ section for more details. + +- key: 2 + name: material_uuid + type: uuid + description: + - Universally unique identifier of the material. + - If not specified, can be deduced from `brand_uuid` + `material_name`. + - See _UUID_ section for more details. + +- key: 3 + name: brand_uuid + type: uuid + description: + - Universally unique identifier of the brand + - If not specified, can be deduced from the `brand_name` string. + - See _UUID_ section for more details. + +- key: 4 + name: gtin + type: number + required: recommended + description: Global Trade Item Number. + +- key: 5 + name: brand_specific_instance_id + type: string + max_length: 16 + description: + - Brand-specific identifier of the package instance. + - Not much use cases at this moment, possibly just for URL deduction + +- key: 6 + name: brand_specific_package_id + type: string + max_length: 16 + description: + - Brand-specific identifier of the package (product ID). + - Not much use cases at this moment, possibly just for URL deduction + +- key: 7 + name: brand_specific_material_id + type: string + max_length: 16 + description: + - Together with brand uniquely identifies each material. + - Not much use cases at this moment, possibly just for URL deduction. + +- key: 8 + name: material_class + type: enum + required: true + example: FFF + items_file: material_class_enum.yaml + display_name_field: description + +- key: 9 + name: material_type + type: enum + category: fff + items_file: material_type_enum.yaml + name_field: abbreviation + display_name_field: name + required: recommended + example: PC + description: + - 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. + +- key: 10 + name: material_name + type: string + max_length: 63 + example: PC Blend Carbon Fiber Black + 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". + +- key: 52 + name: material_abbreviation + type: string + max_length: 7 + example: PCCF + description: + - Abbreviation of the material name, for UI purposes (footers, dashboards, ...). + - If not present, the material inherits the abbreviation from the material type. + +- key: 11 + name: brand_name + type: string + max_length: 31 + required: recommended + description: Brand of the material. + example: Prusament + +- key: 12 + deprecated: true + +- key: 13 + name: write_protection + type: enum + items_file: write_protection_enum.yaml + description: + - Indicates whether the tag is write protected (everything except aux section, that one should be always writable). + - See the _Write protection_ section. + +- key: 14 + name: manufactured_date + type: timestamp + required: recommended + +- key: 55 + name: country_of_origin + type: string + max_length: 2 + description: Country the [MaterialPackageInstance](terminology) was produced in, encoded as a two-letter code according to [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). + +- key: 15 + name: expiration_date + type: timestamp + +- key: 16 + name: nominal_netto_full_weight + required: recommended + type: number + unit: g + example: 1000 + description: + - Nominal/advertised weight of the full package of the material, excluding the container. + - The actual netto weight of a specific package instance can slightly differ and is specified by `actual_netto_full_weight`. + +- key: 17 + name: actual_netto_full_weight + required: recommended + type: number + unit: g + example: 1012 + description: + - Actual weight of the full package of the material of this specific package instance, excluding the weight of the container. + - Can slightly differ from `nominal_netto_full_weight`. + - If not present, `nominal_netto_full_weight` SHALL be used as a fallback. + +- key: 53 + name: nominal_full_length + required: recommended + category: fff + type: number + unit: mm + example: 350000 + description: + - Nominal/advertised filament length of the full spool. + - The actual length of a specific package instance can slightly differ and is specified by `actual_full_length` + +- key: 54 + name: actual_full_length + required: recommended + category: fff + type: number + unit: mm + example: 351000 + description: + - Actual filament length of the full spool. + - Can slightly differ from `nominal_full_length`. + - If not present, `nominal_full_length` SHALL be used as a fallback. + +- key: 18 + name: empty_container_weight + required: recommended + type: number + unit: g + description: Weight of the empty container. + +- key: 19 + name: primary_color + type: color_rgba + required: recommended + example: "`\\xff\\x00\\x00\\x7f`" + description: + - Primary color of the material in the RGB(A) format, intended for UI purposes. + - 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 + description: + - One of secondary colors of the material. + - Data format is the same as for `primary_color`. + +- key: 21 + name: secondary_color_1 + type: color_rgba + description: See `secondary_color_0`. + +- key: 22 + name: secondary_color_2 + type: color_rgba + description: See `secondary_color_0`. + +- key: 23 + name: secondary_color_3 + type: color_rgba + description: See `secondary_color_0`. + +- key: 24 + name: secondary_color_4 + type: color_rgba + description: See `secondary_color_0`. + +- key: 25 + deprecated: true + +- key: 26 + deprecated: true + +- key: 27 + name: transmission_distance + type: number + example: 6.6 + unit: HueForge TD + description: + - Transmission Distance is a number representing material opacity. + - Value ranges from 0.1 (least transparent/most opaque) to 100 (most transparent/least opaque). + - See [Prusa TD values](https://help.prusa3d.com/article/hueforge-filament-transparency-values-and-hexcodes_762314) or [HueForge website](https://shop.thehueforge.com/blogs/news/what-is-hueforge). + +- key: 28 + name: tags + type: enum_array + max_length: 16 + items_file: tags_enum.yaml + example: "glitter + dual_color" + required: recommended + description: Properties of the material. Can have multiple tags at once. + +- key: 56 + name: certifications + type: enum_array + max_length: 8 + items_file: material_certifications_enum.yaml + example: "`ul_2818`" + description: Certifications the material has. + +- key: 29 + name: density + type: number + unit: g/cm³ (1 g/cm³ = 0.001 g/mm³ = 1000 kg/m³) + example: 1.24 + required: recommended + description: Density of the material. + +# Removed 30 - filament_diameter, which expressed the diameter in mm. +# Replaced by filament_diameter_v2 (key 61) using µm, to match the architecture + +- key: 61 + name: filament_diameter_v2 + type: int + unit: µm + example: 2850 + category: fff + description: + - Diameter of the filament, in µm. + - If not present, 1750 µm is assumed. + - "**Note:** Replaces the removed key 30 (`filament_diameter`), which was in mm." + +# Removed 18 + +# Removed 19 + +- key: 31 + name: hardness_shore_a + type: int + example: 95 + description: + - Hardness of the material on the Shore A hardness scale (suitable for softer materials). + - "**Note:** There is no 1:1 mapping between A and D scales, different materials can have different values on one scale even though they are the same on the other." + +- key: 32 + name: hardness_shore_d + type: int + example: 30 + description: + - Hardness of the material on the Shore D hardness scale (suitable for harder materials). + - "**Note:** There is no 1:1 mapping between A and D scales, different materials can have different values on one scale even though they are the same on the other." + +# Removed 33 - min_nozzle_diameter, which expressed the diameter in mm. +# Replaced by min_nozzle_diameter_v2 (key 62) using µm, to match the architecture + +- key: 62 + name: min_nozzle_diameter_v2 + type: int + unit: µm + example: 400 + category: fff + description: + - Filaments can contain particles that would clog smaller nozzles. + - This field specifies minimum nozzle diameter recommended for printing this material, in µm. + - "**Note:** Replaces the removed key 33 (`min_nozzle_diameter`), which was in mm." + +- key: 34 + name: min_print_temperature + type: int + unit: °C + example: 205 + required: recommended + category: fff + description: + - Minimum recommended nozzle temperature for printing. + - Also used for loading the filament to the nozzle. + +- key: 35 + name: max_print_temperature + type: int + unit: °C + example: 225 + required: recommended + category: fff + description: + - Maximum recommended nozzle temperature for printing. + - Also used for loading the filament to the nozzle. + +- key: 36 + name: preheat_temperature + type: int + unit: °C + example: 170 + category: fff + required: recommended + description: + - Recommended nozzle temperature for preheating/load cell bed leveling. + - Should be large enough for the material to get soft, but not low enough for it no to drip out of the nozzle. + +- key: 37 + name: min_bed_temperature + type: int + unit: °C + example: 60 + category: fff + required: recommended + description: + - Minimum recommended heatbed temperature. + +- key: 38 + name: max_bed_temperature + type: int + unit: °C + example: 60 + category: fff + required: recommended + description: + - Maximum recommended heatbed temperature. + +- key: 39 + name: min_chamber_temperature + type: int + unit: °C + example: 10 + category: fff + description: + - Minimum recommended temperature of the chamber. + +- key: 40 + name: max_chamber_temperature + type: int + unit: °C + example: 50 + category: fff + description: + - Maximum recommended temperature of the chamber. + +- key: 41 + name: chamber_temperature + type: int + unit: °C + example: 20 + category: fff + description: + - Ideal chamber temperature for printing. + +- key: 42 + name: container_width + type: int + unit: mm + example: 75 + category: fff + description: + - Width of the filament spool. Can be useful to know for spool holders, dry boxes and such. + +- key: 43 + name: container_outer_diameter + type: int + unit: mm + example: 200 + category: fff + description: + - Diameter of the spool. Can be useful to know for spool holders, dry boxes and such. + +- key: 44 + name: container_inner_diameter + type: int + unit: mm + example: 100 + category: fff + description: + - Diameter of the inner cylinder the filament is spooled once. + - Equals to the minimum diameter of the filament winding. + +- key: 45 + name: container_hole_diameter + type: int + unit: mm + example: 52 + category: fff + description: + - Diameter of the center hole of the spool. + +- key: 46 + name: viscosity_18c + type: number + unit: mPa·s + description: Viscosity of the material at 18 °C. + category: sla + +- key: 47 + name: viscosity_25c + type: number + unit: mPa·s + description: Viscosity of the material at 25 °C. + category: sla + example: 80 + +- key: 48 + name: viscosity_40c + type: number + unit: mPa·s + description: Viscosity of the material at 40 °C. + category: sla + +- key: 49 + name: viscosity_60c + type: number + unit: mPa·s + description: Viscosity of the material at 60 °C. + category: sla + +- key: 50 + name: container_volumetric_capacity + type: number + unit: ml (cm³) + category: sla + description: Maximum amount of material the container can hold. + +- key: 51 + name: cure_wavelength + type: int + unit: nm + example: 405 + category: sla + description: + - Wavelength of the light the material has been designed to be cured with. + +- key: 57 + name: drying_temperature + type: int + unit: °C + example: 45 + category: fff + description: + - Recommended ambient temperature for drying. + +- key: 58 + name: drying_time + type: int + unit: min + example: 480 + category: fff + description: + - Recommended drying time (at `drying_temperature`). + +# First unused key: 63 diff --git a/data/material_class_enum.yaml b/data/material_class_enum.yaml index 84d37d9..48ab0d7 100644 --- a/data/material_class_enum.yaml +++ b/data/material_class_enum.yaml @@ -1,7 +1,7 @@ -- key: 0 - name: FFF - description: Filament - -- key: 1 - name: SLA - description: Resin +- key: 0 + name: FFF + description: Filament + +- key: 1 + name: SLA + description: Resin diff --git a/docs_src/README.md b/docs_src/README.md index 2a7cfe5..a12e128 100644 --- a/docs_src/README.md +++ b/docs_src/README.md @@ -1,6 +1,6 @@ # OpenPrintTag Specification -This website contains specifications for the [OpenPrintTag project](//openprinttag.org). It is automatically generated from the [OpenPrintTag github repository](//github.com/prusa3d/OpenPrintTag). +This website contains specifications for the [OpenPrintTag project](//openprinttag.org). It is automatically generated from the [OpenPrintTag Specification repository]({{repo}}). **Please note that the specification is still work in progress.** We are collecting feedback from interested parties and improving things. @@ -8,7 +8,8 @@ If you are interested in participating in the project, please see [Contributing] ## Quick links * [OpenPrintTag main website](//openprinttag.org) -* [Github repository](//github.com/prusa3d/OpenPrintTag) +* [OpenPrintTag Architecture website]({{arch}}) +* [OpenPrintTag Specification repository]({{repo}}) * [Data format specification](nfc_data_format.md) * Electrical & hardware specification - will be added soon™ * [Examples](examples.md) diff --git a/docs_src/_sidebar.md b/docs_src/_sidebar.md index 379248e..39d33d3 100644 --- a/docs_src/_sidebar.md +++ b/docs_src/_sidebar.md @@ -1,10 +1,11 @@ - [Home](/) - [Terminology](/terminology) - [Data format](/nfc_data_format) -- [Material types](/material_types) +- [FFF material types](/fff_material_types) - [Material tags](/material_tags) - [Material certifications](/material_certifications) - [Technical details](/nfc_technical_details) - [Physical specification](/physical_spec) - [Examples](/examples) +- [Changelog](/changelog) - [Contributing](/contributing) diff --git a/docs_src/changelog.md b/docs_src/changelog.md new file mode 100644 index 0000000..44a7fd5 --- /dev/null +++ b/docs_src/changelog.md @@ -0,0 +1,5 @@ +# Changelog + +## Not yet released +1. `filament_diameter` (mm) replaced with `filament_diameter_v2` (µm) to align with the OpenPrintTag architecture +1. `min_nozzle_diameter` (mm) replaced with `min_nozzle_diameter_v2` (µm) to align with the OpenPrintTag architecture diff --git a/docs_src/material_types.md b/docs_src/fff_material_types.md similarity index 64% rename from docs_src/material_types.md rename to docs_src/fff_material_types.md index 46993f5..9fef337 100644 --- a/docs_src/material_types.md +++ b/docs_src/fff_material_types.md @@ -1,4 +1,4 @@ -# Material types +# FFF material types Material types are a FFF-only mechanism to categorize materials into broad, strictly and universally defined categories. See [Terminology](terminology.md). -{{ enum_table("material_type_enum", material_type_columns) }} +{{ enum_table("material_type_enum", fff_material_type_columns) }} diff --git a/docs_src/generate.py b/docs_src/generate.py index ed4dfb0..e39f812 100644 --- a/docs_src/generate.py +++ b/docs_src/generate.py @@ -59,14 +59,15 @@ def gen_material_tag_table(): r.write("
".join(desc_lines)) r.write("") - r.write("") + r.write("\n\n") + r.write(f"*This table was automatically generated from [`tags_enum.yaml`]({vars.repo}/blob/main/data/tags_enum.yaml) and [`tag_categories_enum.yaml`]({vars.repo}/blob/main/data/tag_categories_enum.yaml)*\n\n") return r.getvalue() env.globals["material_tag_table"] = gen_material_tag_table -env.globals["material_type_columns"] = [ +env.globals["fff_material_type_columns"] = [ Column(field="key", title="Key"), Column(field="abbreviation", title="Name", transform=lambda x: f"`{x}`"), Column(field="name", title="Full name"), @@ -83,13 +84,14 @@ def gen_material_tag_table(): gen_doc_file("_navbar") gen_doc_file("_sidebar") gen_doc_file("README") +gen_doc_file("changelog") gen_doc_file("terminology") gen_doc_file("nfc_data_format") gen_doc_file("nfc_technical_details") gen_doc_file("examples") gen_doc_file("contributing") -gen_doc_file("material_types") +gen_doc_file("fff_material_types") gen_doc_file("material_tags") gen_doc_file("material_certifications") gen_doc_file("physical_spec") diff --git a/docs_src/generate_common.py b/docs_src/generate_common.py index 6025b85..a4ee4ea 100644 --- a/docs_src/generate_common.py +++ b/docs_src/generate_common.py @@ -121,6 +121,7 @@ def show_file(file, language="yaml"): # Other variables env.globals["repo"] = repo +env.globals["arch"] = arch # Generate documentation files diff --git a/docs_src/material_certifications.md b/docs_src/material_certifications.md index ba86744..503cb0a 100644 --- a/docs_src/material_certifications.md +++ b/docs_src/material_certifications.md @@ -1,6 +1,6 @@ # Material certifications Material certifications allow expressing what certificates a material has. -The certifications are stored as an enum on the tag to reduce memory usage and promote standard encoding. If there is a certification missing in the enum, please create a PR or file an issue in the [OpenPrintTag repository]({{repo}}). +The certifications are stored as an enum on the tag to reduce memory usage and promote standard encoding. If there is a certification missing in the enum, please create a PR or file an issue in the [OpenPrintTag Architecture repository](//github.com/OpenPrintTag/openprinttag-architecture). {{ enum_table("material_certifications_enum", material_certification_columns) }} diff --git a/docs_src/media/class_diagram.mermaid b/docs_src/media/class_diagram.mermaid deleted file mode 100644 index 7a8cbc5..0000000 --- a/docs_src/media/class_diagram.mermaid +++ /dev/null @@ -1,46 +0,0 @@ -classDiagram - class MaterialProperties { - shore_hardneess - nozzle_print_temperature - } - class MaterialType { - - } - class Material { - properties: MaterialProperties - type: MaterialType - brand - name - abbreviation - primary_color - } - MaterialType --> Material - MaterialProperties --> Material - - class MaterialContainer { - inner_diameter - outer_diameter - width - empty_weight - } - - class MaterialPackage { - material: Material - container: MaterialContainer - gtin - filament_diameter - nominal_full_weight - } - Material --> MaterialPackage - MaterialContainer --> MaterialPackage - - class DynamicData { - consumed_weight - } - class MaterialPackageInstance { - package: MaterialPackage - dynamic_data: DynamicData - actual_full_length - } - MaterialPackage --> MaterialPackageInstance - DynamicData --> MaterialPackageInstance diff --git a/docs_src/media/class_diagram.svg b/docs_src/media/class_diagram.svg deleted file mode 100644 index 0121dd3..0000000 --- a/docs_src/media/class_diagram.svg +++ /dev/null @@ -1,102 +0,0 @@ -

MaterialProperties

shore_hardneess

nozzle_print_temperature

MaterialType

Material

properties: MaterialProperties

type: MaterialType

brand

name

abbreviation

primary_color

MaterialContainer

inner_diameter

outer_diameter

width

empty_weight

MaterialPackage

material: Material

container: MaterialContainer

gtin

filament_diameter

nominal_full_weight

DynamicData

consumed_weight

MaterialPackageInstance

package: MaterialPackage

dynamic_data: DynamicData

actual_full_length

diff --git a/docs_src/nfc_data_format.md b/docs_src/nfc_data_format.md index eb8069f..41b8410 100644 --- a/docs_src/nfc_data_format.md +++ b/docs_src/nfc_data_format.md @@ -1,220 +1,227 @@ -# NFC Data Format Specification - -## 1. Used standards -- [ISO/IEC 15693-3 (NFC-V)](https://en.wikipedia.org/wiki/ISO/IEC_15693) -- [NFC Data Exchange Format (NDEF)](https://nfc-forum.org/build/specifications/data-exchange-format-ndef-technical-specification/) -- [Concise Binary Object Representation (CBOR)](https://cbor.io/) - -## 2. General structure - - - - - - - - - - - - - - - - - - - - - - - - - -
CC record
(Capability Container)
NDEF TLVTLV Terminator
TLV Header
NDEF record
NDEF headerMeta regionMain regionAuxiliary region
Meta sectionMain sectionUnused spaceAuxiliary sectionUnused space
- - -1. The top layer of the NFC tag is an NDEF message in a NDEF TLV record. - - The tag MAY contain other TLV records. The NDEF TLV record doesn't have to be the first TLV record. -1. The message has an **NDEF record** of MIME type **application/vnd.openprinttag**. - 1. The NDEF record MUST NOT be split into multiple NDEF record chunks. - - Splitting the record would break the "virtual space" of the payload and would complicate implementation. - 1. The NDEF message MAY contain other NDEF records. The material NDEF record doesn't need to be the first NDEF record in the message. -1. The payload of the OpentPrintTag NDEF record consists of: - 1. **Meta section** (CBOR map) - 1. Always at the beginning of the payload. - 1. Contains information about other regions: - - Region is a part of the payload allocated for the respective section. - - A section does not have to fill the whole region. - 1. **Main section** (CBOR map) - 1. Positioned at the beginning of the main region. - 1. Intended for static information, not intended to be updated by printers. - - The only situation where this region needs to be updated would be when the container is being repurposed. - 1. **Auxiliary section** (optional, CBOR map) - 1. Positioned at the beginning of the auxiliary region. - 1. Intended for dynamic information, intended to be updated by the printers. - 1. SHALL have at least 16 B allocated if present. 32 B is recommended. - 1. The root map of the section SHOULD be encoded as an indefinite-length container. - -1. Unused space in the sections (outside of the region CBOR) SHALL NOT contain any meaningful working data. It SHOULD be filled with zeroes on tag initialization, but there are no requirements on upkeeping that afterwards. Users CAN update the regions with smaller data, leaving remnants of the original data behind. - -## 3. Specification common to all sections - -### 3.1 CBOR data representation -1. Data of all sections in the specification are represented as a CBOR map. - 1. Keys of the map are integers. Semantics of the keys are specific to each section. - 1. All data sections SHALL be at most 512 bytes long. - 1. All fields MUST follow this specification. Using custom or vendor-specific keys is not permitted (with the exception described in the Aux Region section). - 1. New keys can be added to the specification at any time, implementations SHALL be able to skip unknown keys, of any type. Unknown fields SHALL NOT be removed when updating a known field (or in any update proces in general) unless explicitly intended. - 1. This applies to unknown items of `enum` and `enum_array` as well, provided that they are represented as integer values in the range of <0, 65535>. - 1. Keys can be deprecated at any time. Deprecated keys will never be reused. - 1. The keys MAY be arbitrarily ordered within the CBOR map. Implementations SHALL support unsorted (non-canonical) CBOR maps. -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. `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. - -#### 3.2.0.1 When reading a UUID -1. First, the program MUST check if an appropriate `XX_uuid` field is present in the data. If so, the program MUST use that UUID. -1. If the field is not present, the program SHOULD derive the UUID using the algorithm described below, and use it. -1. If the derivation algorithm is not implemented or if the fields required for the derivation are not present and cannot be derived, the UUID is considered not defined and the entity CANNOT be uniquely referenced. - -#### 3.2.0.2 When preparing/writing main region data -1. The program MAY omit the `XX_uuid` field from the data if the value is equal to the auto-derived UUID (and thus if the reader would return the same result in both cases). -1. Unless guaranteed that a tag is being used for the first time, the program SHOULD assign a new, unique `instance_uuid` (presumably UUIDv4) and include it in the data. This is to prevent `instance_uuid` collisions when a tag is being reused. - 1. If there is a possibility that the same generated data would get written to multiple tags, the program SHOULD NOT include `instance_uuid`, and instead rely on the auto-derivation mechanism that would yield different UUIDs for different tags. - -#### 3.2.0.3 Example: Brand renaming -If a brand decides to change name but wants to keep the original `brand_uuid` that was auto-derived from its name, it needs to start adding `brand_uuid` field with the original UUID: -1. `brand_name = Prusament` (present in the data), `brand_uuid = ae5ff34e-298e-50c9-8f77-92a97fb30b0` (not present, can be automatically derived) -1. Brand gets renamed to `Pepament` -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 -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. -1. Numbers are encoded as decimal strings. -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`). - -| UUID | 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` | -| `package_uuid` | `N + brand_uuid + gtin` | `6f7d485e-db8d-4979-904e-a231cd6602b2` | -| `instance_uuid` | `N + nfc_tag_uid` | `31062f81-b5bd-4f86-a5f8-46367e841508` | - - -For example: -{% python %} -import uuid - -def generate_uuid(namespace, *args): - return uuid.uuid5(uuid.UUID(namespace), b"".join(args)) - -brand_namespace = "5269dfb7-1559-440a-85be-aba5f3eff2d2" -brand_name = "Prusament" -brand_uuid = generate_uuid(brand_namespace, brand_name.encode("utf-8")) -print(f"brand_uuid = {brand_uuid}") - -material_namespace = "616fc86d-7d99-4953-96c7-46d2836b9be9" -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}") - -package_namespace = "6f7d485e-db8d-4979-904e-a231cd6602b2" -gtin = "1234" -package_uuid = generate_uuid(package_namespace, brand_uuid.bytes, gtin.encode("utf-8")) -print(f"package_uuid = {package_uuid}") - -instance_namespace = "31062f81-b5bd-4f86-a5f8-46367e841508" -nfc_tag_uid = b"\xE0\x04\x01\x08\x66\x2F\x6F\xBC" -instance_uuid = generate_uuid(instance_namespace, nfc_tag_uid) -print(f"instance_uuid = {instance_uuid}") -{% endpython %} - -## 4. Meta section - -The meta section allows defining of region offsets (within the NDEF payload) and sizes. -- Main region is always present. If the offset is not specified, the region starts right after the meta section. -- Auxiliary region is optional (although heavily recommended). Its presence is indicated by the `aux_region_offset` field. - -### 4.1 Field list -{{ fields_table("meta_fields") }} - -## 5. Main section -The main section contains material information that does not change during the package instance lifetime. - - This section can possibly be locked by the manufacturer. - -### 5.1 Field list -{{ fields_table("main_fields", "") }} - -#### 5.1.1 Field list (FFF-specific) -{{ fields_table("main_fields", "fff") }} - -#### 5.1.2 Field list (SLA-specific) -{{ fields_table("main_fields", "sla") }} - -#### 5.1.3 `material_class` items -{{ enum_table("material_class_enum") }} - -#### 5.1.4 `material_type` -See [Material types](/material_types) - -#### 5.1.5 `tags` -See [Material tags](/material_tags) - -#### 5.1.6 `write_protection` items -{{ enum_table("write_protection_enum") }} - -## 6. Auxiliary section -The auxiliary section is intended for dynamic data - typically usage tracking. - -### 6.1 Field list -{{ fields_table("aux_fields", "") }} - -#### 6.1.1 Field list (SLA-specific) -{{ fields_table("aux_fields", "sla") }} - -#### 6.1.1 Field list (user data) -{{ fields_table("aux_fields", "user_data") }} - -### 6.2 Vendor-specific fields -Vendor-specific fields not specified in this document are permitted for keys specified by the following table. Vendors may contact the specification authority to be assigned a key range for them to use. - -| Min key | Max key | Vendor | -| --- | --- | --- | -| 65400 | 65534 | General purpose | -| 65300 | 65400 | Prusa | - -#### 6.2.1 General purpose key range -The "General purpose" key range MAY be used by anyone, provided they follow the following rules: -1. Users MUST assign themselves a unique enough `general_purpose_range_user` value. This is done with no central authority. -1. The range MUST be used only by one user at a time, determined by the `general_purpose_range_user` field. -1. Users MUST ensure that the `general_purpose_range_user` field is set to the value assigned to them for any read or write access to the general purpose range. -1. Users MUST delete any general purpose range fields present before changing the value of `general_purpose_range_user`. +# NFC Data Format Specification + +## 1. Used standards +- [ISO/IEC 15693-3 (NFC-V)](https://en.wikipedia.org/wiki/ISO/IEC_15693) +- [NFC Data Exchange Format (NDEF)](https://nfc-forum.org/build/specifications/data-exchange-format-ndef-technical-specification/) +- [Concise Binary Object Representation (CBOR)](https://cbor.io/) + +## 2. General structure + + + + + + + + + + + + + + + + + + + + + + + + + +
CC record
(Capability Container)
NDEF TLVTLV Terminator
TLV Header
NDEF record
NDEF headerMeta regionMain regionAuxiliary region
Meta sectionMain sectionUnused spaceAuxiliary sectionUnused space
+ + +1. The top layer of the NFC tag is an NDEF message in a NDEF TLV record. + - The tag MAY contain other TLV records. The NDEF TLV record doesn't have to be the first TLV record. +1. The message has an **NDEF record** of MIME type **application/vnd.openprinttag**. + 1. The NDEF record MUST NOT be split into multiple NDEF record chunks. + - Splitting the record would break the "virtual space" of the payload and would complicate implementation. + 1. The NDEF message MAY contain other NDEF records. The material NDEF record doesn't need to be the first NDEF record in the message. +1. The payload of the OpentPrintTag NDEF record consists of: + 1. **Meta section** (CBOR map) + 1. Always at the beginning of the payload. + 1. Contains information about other regions: + - Region is a part of the payload allocated for the respective section. + - A section does not have to fill the whole region. + 1. **Main section** (CBOR map) + 1. Positioned at the beginning of the main region. + 1. Intended for static information, not intended to be updated by printers. + - The only situation where this region needs to be updated would be when the container is being repurposed. + 1. **Auxiliary section** (optional, CBOR map) + 1. Positioned at the beginning of the auxiliary region. + 1. Intended for dynamic information, intended to be updated by the printers. + 1. SHALL have at least 16 B allocated if present. 32 B is recommended. + 1. The root map of the section SHOULD be encoded as an indefinite-length container. + +1. Unused space in the sections (outside of the region CBOR) SHALL NOT contain any meaningful working data. It SHOULD be filled with zeroes on tag initialization, but there are no requirements on upkeeping that afterwards. Users CAN update the regions with smaller data, leaving remnants of the original data behind. + +## 3. Specification common to all sections + +### 3.1 CBOR data representation +1. Data of all sections in the specification are represented as a CBOR map. + 1. Keys of the map are integers. Semantics of the keys are specific to each section. + 1. All data sections SHALL be at most 512 bytes long. + 1. All fields MUST follow this specification. Using custom or vendor-specific keys is not permitted (with the exception described in the Aux Region section). + 1. New keys can be added to the specification at any time, implementations SHALL be able to skip unknown keys, of any type. Unknown fields SHALL NOT be removed when updating a known field (or in any update proces in general) unless explicitly intended. + 1. This applies to unknown items of `enum` and `enum_array` as well, provided that they are represented as integer values in the range of <0, 65535>. + 1. Keys can be deprecated at any time. Deprecated keys will never be reused. + 1. The keys MAY be arbitrarily ordered within the CBOR map. Implementations SHALL support unsorted (non-canonical) CBOR maps. +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. `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 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. + +#### 3.2.0.1 When reading a UUID +1. First, the program MUST check if an appropriate `XX_uuid` field is present in the data. If so, the program MUST use that UUID. +1. If the field is not present, the program SHOULD derive the UUID using the algorithm described below, and use it. +1. If the derivation algorithm is not implemented or if the fields required for the derivation are not present and cannot be derived, the UUID is considered not defined and the entity CANNOT be uniquely referenced. + +#### 3.2.0.2 When preparing/writing main region data +1. The program MAY omit the `XX_uuid` field from the data if the value is equal to the auto-derived UUID (and thus if the reader would return the same result in both cases). +1. Unless guaranteed that a tag is being used for the first time, the program SHOULD assign a new, unique `instance_uuid` (presumably UUIDv4) and include it in the data. This is to prevent `instance_uuid` collisions when a tag is being reused. + 1. If there is a possibility that the same generated data would get written to multiple tags, the program SHOULD NOT include `instance_uuid`, and instead rely on the auto-derivation mechanism that would yield different UUIDs for different tags. + +#### 3.2.0.3 Example: Brand renaming +If a brand decides to change name but wants to keep the original `brand_uuid` that was auto-derived from its name, it needs to start adding `brand_uuid` field with the original UUID: +1. `brand_name = Prusament` (present in the data), `brand_uuid = ae5ff34e-298e-50c9-8f77-92a97fb30b09` (not present, can be automatically derived) +1. Brand gets renamed to `Pepament` +1. `brand_name = Pepament` (present in the data), `brand_uuid = ae5ff34e-298e-50c9-8f77-92a97fb30b09` (present in the data) + + +### 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. +1. Numbers are encoded as decimal strings. +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`). + +| UUID | 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` | +| `package_uuid` | `N + brand_uuid + gtin` | `6f7d485e-db8d-4979-904e-a231cd6602b2` | +| `instance_uuid` | `N + nfc_tag_uid` | `31062f81-b5bd-4f86-a5f8-46367e841508` | + + +For example: +{% python %} +import uuid + +def generate_uuid(namespace, *args): + return uuid.uuid5(uuid.UUID(namespace), b"".join(args)) + +brand_namespace = "5269dfb7-1559-440a-85be-aba5f3eff2d2" +brand_name = "Prusament" +brand_uuid = generate_uuid(brand_namespace, brand_name.encode("utf-8")) +print(f"brand_uuid = {brand_uuid}") + +material_namespace = "616fc86d-7d99-4953-96c7-46d2836b9be9" +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}") + +package_namespace = "6f7d485e-db8d-4979-904e-a231cd6602b2" +gtin = "1234" +package_uuid = generate_uuid(package_namespace, brand_uuid.bytes, gtin.encode("utf-8")) +print(f"package_uuid = {package_uuid}") + +instance_namespace = "31062f81-b5bd-4f86-a5f8-46367e841508" +nfc_tag_uid = b"\xE0\x04\x01\x08\x66\x2F\x6F\xBC" +instance_uuid = generate_uuid(instance_namespace, nfc_tag_uid) +print(f"instance_uuid = {instance_uuid}") +{% endpython %} + +## 4. Meta section + +The meta section allows defining of region offsets (within the NDEF payload) and sizes. +- Main region is always present. If the offset is not specified, the region starts right after the meta section. +- Auxiliary region is optional (although heavily recommended). Its presence is indicated by the `aux_region_offset` field. + +### 4.1 Field list +{{ fields_table("meta_fields") }} + +## 5. Main section +The main section contains material information that does not change during the package instance lifetime. + - This section can possibly be locked by the manufacturer. + +### 5.1 Field list +{{ fields_table("main_fields", "") }} + +#### 5.1.1 Field list (FFF-specific) +{{ fields_table("main_fields", "fff") }} + +##### 5.1.1.1 Removed fields (FFF-specific) + +|Name|Key|Removal reason| +|:--|:--|:--| +|`filament_diameter`|30|Unit change (`mm` → `µm`) to align with the OpenPrintTag Architecture.
Replaced with `filament_diameter_v2`.| +|`min_nozzle_diameter`|33|Unit change (`mm` → `µm`) to align with the OpenPrintTag Architecture.
Replaced with `min_nozzle_diameter_v2`.| + +#### 5.1.2 Field list (SLA-specific) +{{ fields_table("main_fields", "sla") }} + +#### 5.1.3 `material_class` items +{{ enum_table("material_class_enum") }} + +#### 5.1.4 `material_type` +See [FFF material types](/fff_material_types) + +#### 5.1.5 `tags` +See [Material tags](/material_tags) + +#### 5.1.6 `write_protection` items +{{ enum_table("write_protection_enum") }} + +## 6. Auxiliary section +The auxiliary section is intended for dynamic data - typically usage tracking. + +### 6.1 Field list +{{ fields_table("aux_fields", "") }} + +#### 6.1.1 Field list (SLA-specific) +{{ fields_table("aux_fields", "sla") }} + +#### 6.1.1 Field list (user data) +{{ fields_table("aux_fields", "user_data") }} + +### 6.2 Vendor-specific fields +Vendor-specific fields not specified in this document are permitted for keys specified by the following table. Vendors may contact the specification authority to be assigned a key range for them to use. + +| Min key | Max key | Vendor | +| --- | --- | --- | +| 65400 | 65534 | General purpose | +| 65300 | 65400 | Prusa | + +#### 6.2.1 General purpose key range +The "General purpose" key range MAY be used by anyone, provided they follow the following rules: +1. Users MUST assign themselves a unique enough `general_purpose_range_user` value. This is done with no central authority. +1. The range MUST be used only by one user at a time, determined by the `general_purpose_range_user` field. +1. Users MUST ensure that the `general_purpose_range_user` field is set to the value assigned to them for any read or write access to the general purpose range. +1. Users MUST delete any general purpose range fields present before changing the value of `general_purpose_range_user`. diff --git a/docs_src/tables.py b/docs_src/tables.py index 7e70b19..62b1104 100644 --- a/docs_src/tables.py +++ b/docs_src/tables.py @@ -1,102 +1,102 @@ -import yaml -import typing -import io -import os - -from vars import * - - -def default_transform(data: any): - match data: - case None: - return "" - - case bool(): - return "yes" if data else "no" - - case list(): - return "
".join(str(x) for x in data) - - case _: - return str(data).replace("\n", "
") - - -def desc_transform(cell: any, row: any): - match cell: - case None: - result = "" - - case list(): - result = "\n".join(str(x) for x in cell) - - case str(): - result = cell - - case _: - assert False - - match row.get("required"): - case True: - result += "\n\n**Required field.**" - - case False | None: - pass - - case "recommended": - result += "\n\n**Recommended field.**" - - case _: - assert False - - return result.strip().replace("\n", "
") - - -class Column(typing.NamedTuple): - field: str - title: str - transform: any = default_transform - transform_ext: any = None - - -def generate_table(yaml_file: str, columns: typing.List[Column], filter: any = None): - src = open(yaml_file, "r", encoding="utf-8") - data = yaml.safe_load(src) - - tgt = io.StringIO("") - basename = os.path.basename(yaml_file) - - # Generate table header - tgt.write("|") - for col in columns: - tgt.write(col.title + "|") - tgt.write("\n") - - tgt.write("|") - for col in columns: - tgt.write(":--|") - tgt.write("\n") - - for row in data: - if filter and not filter(row): - continue - - if row.get("deprecated", False): - continue - - tgt.write("|") - for col in columns: - cell = row.get(col.field, None) - - if col.transform_ext: - cell = col.transform_ext(cell, row) - else: - cell = col.transform(cell) - - tgt.write(cell) - tgt.write("|") - tgt.write("\n") - - tgt.write("\n") - tgt.write(f"*This table was automatically generated from [`{basename}`]({repo}/blob/main/data/{basename})*\n\n") - - return tgt.getvalue() +import yaml +import typing +import io +import os + +from vars import * + + +def default_transform(data: any): + match data: + case None: + return "" + + case bool(): + return "yes" if data else "no" + + case list(): + return "
".join(str(x) for x in data) + + case _: + return str(data).replace("\n", "
") + + +def desc_transform(cell: any, row: any): + match cell: + case None: + result = "" + + case list(): + result = "\n".join(str(x) for x in cell) + + case str(): + result = cell + + case _: + assert False + + match row.get("required"): + case True: + result += "\n\n**Required field.**" + + case False | None: + pass + + case "recommended": + result += "\n\n**Recommended field.**" + + case _: + assert False + + return result.strip().replace("\n", "
") + + +class Column(typing.NamedTuple): + field: str + title: str + transform: any = default_transform + transform_ext: any = None + + +def generate_table(yaml_file: str, columns: typing.List[Column], filter: any = None): + src = open(yaml_file, "r", encoding="utf-8") + data = yaml.safe_load(src) + + tgt = io.StringIO("") + basename = os.path.basename(yaml_file) + + # Generate table header + tgt.write("|") + for col in columns: + tgt.write(col.title + "|") + tgt.write("\n") + + tgt.write("|") + for col in columns: + tgt.write(":--|") + tgt.write("\n") + + for row in data: + if filter and not filter(row): + continue + + if row.get("deprecated", False): + continue + + tgt.write("|") + for col in columns: + cell = row.get(col.field, None) + + if col.transform_ext: + cell = col.transform_ext(cell, row) + else: + cell = col.transform(cell) + + tgt.write(cell) + tgt.write("|") + tgt.write("\n") + + tgt.write("\n") + tgt.write(f"*This table was automatically generated from [`{basename}`]({repo}/blob/main/data/{basename})*\n\n") + + return tgt.getvalue() diff --git a/docs_src/terminology.md b/docs_src/terminology.md index 993f30b..7d737d0 100644 --- a/docs_src/terminology.md +++ b/docs_src/terminology.md @@ -1,16 +1,6 @@ -# Terminology -The OpenPrintTag format is based on the following entity model (not all fields are present): - - - -1. `Material` represents a material with some color and properties. - 1. For example "Prusament PLA Prusa Galaxy Black". -1. `MaterialType` is used to categorize materials into broad, strictly and universally defined categories. - 1. For FFF, base polymers are used for the categorization. - 1. SLA resins are not clearly categorizable, so they don't use `MaterialType`. -1. `MaterialPackage` represents a material that is packaged in some quantity (in case of FFF, the material is also extruded with a specific diameter) and put in a container. - 1. For example [Prusament PLA Prusa Galaxy Black 1kg](https://www.prusa3d.com/cs/produkt/prusament-pla-prusa-galaxy-black-1kg/). -1. `MaterialPackageInstance` represents a single specific spool of filament (or a single specific bottle of resin in case of SLA) - 1. For example [this specific spool of PLA Prusa Galaxy Black](https://prusament.com/spool/?spoolId=eb8881e3e0). -1. `MaterialContainer` represents a container the material is stored in in `MaterialPackage`. For filaments, this would be a spool, for resins, this would be a bottle. - 1. For example "Prusament 1kg spool" +# Terminology + +The OpenPrintTag format is based on an entity model described in the [OpenPrintTag Architecture]({{arch}}). Please visit the architecture page, most significantly: + +* [Materials]({{arch}}/#/materials): `Material`, `FFFMaterialType`, `MaterialTag`, ... +* [Packaging]({{arch}}/#/packaging): `MaterialPackage`, `MaterialPackageInstance`, `MaterialContainer`, ... diff --git a/docs_src/vars.py b/docs_src/vars.py index db18d90..ef5941b 100644 --- a/docs_src/vars.py +++ b/docs_src/vars.py @@ -1,6 +1,8 @@ import os -repo = "https://github.com/prusa3d/OpenPrintTag" +arch = "https://arch.openprinttag.org" + +repo = "https://github.com/OpenPrintTag/openprinttag-specification" dir = os.path.dirname(__file__) root_dir = f"{dir}/.." out_dir = f"{root_dir}/docs" diff --git a/tests/encode_decode/04_data.bin b/tests/encode_decode/04_data.bin index a242500..a86d09e 100644 Binary files a/tests/encode_decode/04_data.bin and b/tests/encode_decode/04_data.bin differ diff --git a/tests/encode_decode/04_info.yaml b/tests/encode_decode/04_info.yaml index 2d02fd5..89df94e 100644 --- a/tests/encode_decode/04_info.yaml +++ b/tests/encode_decode/04_info.yaml @@ -23,7 +23,6 @@ regions: primary_color: 5 tags: 9 density: 5 - min_nozzle_diameter: 5 min_print_temperature: 5 max_print_temperature: 5 min_bed_temperature: 4 @@ -37,6 +36,7 @@ regions: container_hole_diameter: 4 material_abbreviation: 10 actual_full_length: 7 + min_nozzle_diameter_v2: 5 aux: payload_offset: 220 absolute_offset: 276 @@ -70,7 +70,6 @@ data: - contains_carbon_fiber - contains_carbon density: 1.39 - min_nozzle_diameter: 0.4 min_print_temperature: 360 max_print_temperature: 390 min_bed_temperature: 120 @@ -84,10 +83,11 @@ data: container_hole_diameter: 53 material_abbreviation: PEKK-CF actual_full_length: 226000 + min_nozzle_diameter_v2: 400 aux: {} raw_data: meta: a10218dc - main: bf080009150a7819436172626f6e582050454b4b2d412b4346313520426c61636b0b67334458546563680e1a690c5d9b101902ee111902f4121901361343303030181c9f04181f181eff181df93d8f1821f9366618221901681823190186182518781826188c1827183c1828188c1829185a182a183d182b18c6182c1864182d183518346750454b4b2d434618361a000372d0ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + main: bf080009150a7819436172626f6e582050454b4b2d412b4346313520426c61636b0b67334458546563680e1a690c5d9b101902ee111902f4121901361343303030181c9f04181f181eff181df93d8f18221901681823190186182518781826188c1827183c1828188c1829185a182a183d182b18c6182c1864182d183518346750454b4b2d434618361a000372d0183e190190ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 aux: a000000000000000000000000000000000000000000000000000000000000000000000 uri: https://www.3dxtech.com/ validate: diff --git a/tests/encode_decode/04_input.yaml b/tests/encode_decode/04_input.yaml index 7c200a6..39aa400 100644 --- a/tests/encode_decode/04_input.yaml +++ b/tests/encode_decode/04_input.yaml @@ -25,7 +25,7 @@ data: # country_of_origin: US # Printing parameters - min_nozzle_diameter: 0.4 + min_nozzle_diameter_v2: 400 min_print_temperature: 360 max_print_temperature: 390 min_bed_temperature: 120 diff --git a/utils/schema/fields.schema.json b/utils/schema/fields.schema.json index 9cfe721..3fc0341 100644 --- a/utils/schema/fields.schema.json +++ b/utils/schema/fields.schema.json @@ -126,8 +126,8 @@ "density": { "$ref": "field_types.schema.json#/definitions/number" }, - "filament_diameter": { - "$ref": "field_types.schema.json#/definitions/number" + "filament_diameter_v2": { + "$ref": "field_types.schema.json#/definitions/int" }, "hardness_shore_a": { "$ref": "field_types.schema.json#/definitions/int" @@ -135,8 +135,8 @@ "hardness_shore_d": { "$ref": "field_types.schema.json#/definitions/int" }, - "min_nozzle_diameter": { - "$ref": "field_types.schema.json#/definitions/number" + "min_nozzle_diameter_v2": { + "$ref": "field_types.schema.json#/definitions/int" }, "min_print_temperature": { "$ref": "field_types.schema.json#/definitions/int"