makea-cli 0.2.29__tar.gz → 0.2.31__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {makea_cli-0.2.29 → makea_cli-0.2.31}/CHANGELOG.md +24 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/PKG-INFO +7 -4
- {makea_cli-0.2.29 → makea_cli-0.2.31}/README.md +6 -3
- makea_cli-0.2.31/makea_cli/__init__.py +1 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/api_client.py +15 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/brand/cmd.py +115 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/files/cmd.py +2 -2
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/file_reader.py +256 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/pyproject.toml +1 -1
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/golden_manifest.json +214 -3
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_file_reader.py +66 -0
- makea_cli-0.2.29/makea_cli/__init__.py +0 -1
- {makea_cli-0.2.29 → makea_cli-0.2.31}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/.claude/skills/link-supplier-user/SKILL.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/.gitignore +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea-collection-upload/SKILL.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea-collection-upload/scripts/create_products.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/__main__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/auth_pkce.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/broker.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/_util.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/about/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/about/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/analytics/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/analytics/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/auth/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/auth/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/brand/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/designer/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/designer/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/directory/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/directory/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/document/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/document/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/email/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/email/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/factory/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/factory/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/files/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/flags/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/flags/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/library/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/library/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/metrics/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/metrics/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/orders/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/orders/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/playbook/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/playbook/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/product/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/product/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/production/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/production/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/production/pricing_spec.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/shipping/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/shipping/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/supplier/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/supplier/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/tasks/__init__.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/commands/tasks/cmd.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/companion.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/config.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/errors.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/identity.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/main.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/output.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/posthog_client.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/makea_cli/registry.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/npm/makea-cli/README.md +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/npm/makea-cli/makea-cli.mjs +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/npm/makea-cli/package.json +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_broker.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_companion.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_currency_conversion.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_output_contract.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_persona_commands.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_pm_reads.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_registry.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/tests/test_supplier_scorecard.py +0 -0
- {makea_cli-0.2.29 → makea_cli-0.2.31}/uv.lock +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.31
|
|
4
|
+
|
|
5
|
+
Beeboo files what a brand hands it in the chat (TECH-150).
|
|
6
|
+
|
|
7
|
+
- `designer create pack-file --product <id> --document <document_id>` puts an
|
|
8
|
+
uploaded file on a tech pack section, named as `get tech-pack --readable`
|
|
9
|
+
shows it. A pattern file (DXF, RUL, PLT) goes on Patterns when no section is
|
|
10
|
+
named. A refusal lists the sections or rows that exist.
|
|
11
|
+
- `designer get library-schema` and `designer create library-element` add a
|
|
12
|
+
fabric, trim or other part to the brand's library, with `--document` for a
|
|
13
|
+
photo or data sheet and `--draft` when required fields are still unknown.
|
|
14
|
+
|
|
15
|
+
## 0.2.30
|
|
16
|
+
|
|
17
|
+
Read HP-GL plot files (.plt), the pattern or marker a CAD sends to a cutting
|
|
18
|
+
plotter (TECH-148).
|
|
19
|
+
|
|
20
|
+
- A PLT reads as its size in cm (40 plotter units to the mm), its pens and the
|
|
21
|
+
text plotted on it (piece names and sizes), with a drawing of the plot.
|
|
22
|
+
PCL/PJL-wrapped HP-GL/2 opens too, with or without the extension.
|
|
23
|
+
- Labels are decoded together, so a one-character GBK piece name (领, 前) is
|
|
24
|
+
read as Chinese rather than as Latin letters.
|
|
25
|
+
- A .plt in another plotter language says to send a DXF + RUL instead.
|
|
26
|
+
|
|
3
27
|
## 0.2.29
|
|
4
28
|
|
|
5
29
|
The file reader stays inside a web worker's memory (TECH-145).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: makea-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.31
|
|
4
4
|
Summary: Makea admin CLI (browser login + Makea admin HTTP API)
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: click<9,>=8.1
|
|
@@ -122,7 +122,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
122
122
|
| `makea-cli admin get about` | read | `GET /admin/about/{target}` | one account's About you (`--user`: a supplier account's person gets their factory's page; `--supplier`): facts, removed, unknown, paused, topic keys. | — |
|
|
123
123
|
| `makea-cli admin get active-users` | read | `POST /admin/analytics/query` | Distinct active users per day over the last N days (DAU series). | `analytics-active-users` |
|
|
124
124
|
| `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
|
|
125
|
-
| `makea-cli admin get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
125
|
+
| `makea-cli admin get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
126
126
|
| `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
|
|
127
127
|
| `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
|
|
128
128
|
| `makea-cli admin get growth-snapshot` | read | `GET /admin/growth/snapshots/{id}` | one reconciled Growth Analysis snapshot (default `latest`); `--block` prints one block (headline, gates, review_items, …). | — |
|
|
@@ -199,7 +199,9 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
199
199
|
|---|---|---|---|---|
|
|
200
200
|
| `makea-cli designer create checkout` | write(ask) | `POST /designer/payments/basket/create-session` | a Stripe checkout link for one or more charges. --key repeats: sampling:<sampling_order_id>, production:<production_o… | — |
|
|
201
201
|
| `makea-cli designer create collection` | write(auto) | `POST /designer/collections/create` | create or update a designer collection. | `designer-create-collection` |
|
|
202
|
+
| `makea-cli designer create library-element` | write(auto) | `POST /designer/library_components` | add a fabric, trim or other part to the brand's library; --document attaches a photo or data sheet, --draft when required fields are unknown. | — |
|
|
202
203
|
| `makea-cli designer create note` | write(ask) | `POST /designer/products/{product_id}/notes` | post a message into a product's conversation. | — |
|
|
204
|
+
| `makea-cli designer create pack-file` | write(auto) | `POST /designer/products/{product_id}/tech_spec/files` | put an uploaded file on a tech pack section; a pattern file (DXF, RUL, PLT) goes on Patterns when no section is named. | — |
|
|
203
205
|
| `makea-cli designer create product` | write(ask) | `POST /designer/product/create_or_update_product` | payload must match manufacture/specifications (all frontend fields; categories/subcategories + collection_id in --hel… | `designer-upsert-product` |
|
|
204
206
|
| `makea-cli designer create quote-award` | write(ask) | `POST /designer/quotes/group_pay_prepare` | choose the supplier quote to sample with. --quote is the supplier link id from `designer list supplier-link`; repeat … | — |
|
|
205
207
|
| `makea-cli designer create rfq` | write(ask) | `POST /designer/product/send_rfq` | ask the chosen suppliers to quote on a product. --supplier repeats; --makea-sourcing also asks Makea to source. | — |
|
|
@@ -207,8 +209,9 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
207
209
|
| `makea-cli designer delete sampling-order` | write(never) | `DELETE /designer/sampling_orders/{sampling_order_id}` | delete a DRAFT sample order. The backend refuses any other status. Graded `never`. | — |
|
|
208
210
|
| `makea-cli designer delete shipping-order` | write(never) | `POST /designer/shipping_orders/{shipping_order_id}/cancel` | the endpoint is called cancel but it unlinks every production order and removes the shipment, so it is a delete here.… | — |
|
|
209
211
|
| `makea-cli designer download document` | read | `GET /designer/document/download` | save bytes for user_id + document_id (designer-authenticated; same query contract as admin document download). | `designer-download-document` |
|
|
210
|
-
| `makea-cli designer get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
212
|
+
| `makea-cli designer get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
211
213
|
| `makea-cli designer get collection` | read | `GET /designer/collections/{collection_id}/overview` | one collection with its products, orders and lifecycle in a single read. | — |
|
|
214
|
+
| `makea-cli designer get library-schema` | read | `GET /designer/library_components/schema` | the library's sections and each one's field keys, options and required fields. | — |
|
|
212
215
|
| `makea-cli designer get lifecycle` | read | `GET /designer/product_orders/{product_order_id}/lifecycle` | which stage this product is in. | — |
|
|
213
216
|
| `makea-cli designer get product` | read | `GET /designer/get_product` | one product: status, documents, prices, links. | — |
|
|
214
217
|
| `makea-cli designer get production-price` | read | `GET /designer/production_orders/{production_order_id}/calculate_price` | what the deposit (or the balance, --phase final) comes to for the quantities on the run right now, line by line. | — |
|
|
@@ -251,7 +254,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
251
254
|
|---|---|---|---|---|
|
|
252
255
|
| `makea-cli supplier create note` | write(ask) | `POST /supplier/products/{id}/channels/{key}/notes` | post a message into a product channel you are part of. | — |
|
|
253
256
|
| `makea-cli supplier download document` | read | `GET /supplier/document/download` | save a document you are allowed to read. | — |
|
|
254
|
-
| `makea-cli supplier get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
257
|
+
| `makea-cli supplier get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
255
258
|
| `makea-cli supplier get pack` | read | `GET /supplier/quote_requests/{id}/technical-specifications` | the tech pack for an RFQ, as the factory is allowed to see it (`--readable`: labels, values and blank essentials only). | — |
|
|
256
259
|
| `makea-cli supplier get pack-changes` | read | `GET /supplier/products/{product_id}/specification-change-history` | what changed in the pack since you quoted, field by field. | — |
|
|
257
260
|
| `makea-cli supplier get profile` | read | `GET /supplier/profile` | your own supplier profile. | — |
|
|
@@ -106,7 +106,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
106
106
|
| `makea-cli admin get about` | read | `GET /admin/about/{target}` | one account's About you (`--user`: a supplier account's person gets their factory's page; `--supplier`): facts, removed, unknown, paused, topic keys. | — |
|
|
107
107
|
| `makea-cli admin get active-users` | read | `POST /admin/analytics/query` | Distinct active users per day over the last N days (DAU series). | `analytics-active-users` |
|
|
108
108
|
| `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
|
|
109
|
-
| `makea-cli admin get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
109
|
+
| `makea-cli admin get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
110
110
|
| `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
|
|
111
111
|
| `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
|
|
112
112
|
| `makea-cli admin get growth-snapshot` | read | `GET /admin/growth/snapshots/{id}` | one reconciled Growth Analysis snapshot (default `latest`); `--block` prints one block (headline, gates, review_items, …). | — |
|
|
@@ -183,7 +183,9 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
183
183
|
|---|---|---|---|---|
|
|
184
184
|
| `makea-cli designer create checkout` | write(ask) | `POST /designer/payments/basket/create-session` | a Stripe checkout link for one or more charges. --key repeats: sampling:<sampling_order_id>, production:<production_o… | — |
|
|
185
185
|
| `makea-cli designer create collection` | write(auto) | `POST /designer/collections/create` | create or update a designer collection. | `designer-create-collection` |
|
|
186
|
+
| `makea-cli designer create library-element` | write(auto) | `POST /designer/library_components` | add a fabric, trim or other part to the brand's library; --document attaches a photo or data sheet, --draft when required fields are unknown. | — |
|
|
186
187
|
| `makea-cli designer create note` | write(ask) | `POST /designer/products/{product_id}/notes` | post a message into a product's conversation. | — |
|
|
188
|
+
| `makea-cli designer create pack-file` | write(auto) | `POST /designer/products/{product_id}/tech_spec/files` | put an uploaded file on a tech pack section; a pattern file (DXF, RUL, PLT) goes on Patterns when no section is named. | — |
|
|
187
189
|
| `makea-cli designer create product` | write(ask) | `POST /designer/product/create_or_update_product` | payload must match manufacture/specifications (all frontend fields; categories/subcategories + collection_id in --hel… | `designer-upsert-product` |
|
|
188
190
|
| `makea-cli designer create quote-award` | write(ask) | `POST /designer/quotes/group_pay_prepare` | choose the supplier quote to sample with. --quote is the supplier link id from `designer list supplier-link`; repeat … | — |
|
|
189
191
|
| `makea-cli designer create rfq` | write(ask) | `POST /designer/product/send_rfq` | ask the chosen suppliers to quote on a product. --supplier repeats; --makea-sourcing also asks Makea to source. | — |
|
|
@@ -191,8 +193,9 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
191
193
|
| `makea-cli designer delete sampling-order` | write(never) | `DELETE /designer/sampling_orders/{sampling_order_id}` | delete a DRAFT sample order. The backend refuses any other status. Graded `never`. | — |
|
|
192
194
|
| `makea-cli designer delete shipping-order` | write(never) | `POST /designer/shipping_orders/{shipping_order_id}/cancel` | the endpoint is called cancel but it unlinks every production order and removes the shipment, so it is a delete here.… | — |
|
|
193
195
|
| `makea-cli designer download document` | read | `GET /designer/document/download` | save bytes for user_id + document_id (designer-authenticated; same query contract as admin document download). | `designer-download-document` |
|
|
194
|
-
| `makea-cli designer get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
196
|
+
| `makea-cli designer get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
195
197
|
| `makea-cli designer get collection` | read | `GET /designer/collections/{collection_id}/overview` | one collection with its products, orders and lifecycle in a single read. | — |
|
|
198
|
+
| `makea-cli designer get library-schema` | read | `GET /designer/library_components/schema` | the library's sections and each one's field keys, options and required fields. | — |
|
|
196
199
|
| `makea-cli designer get lifecycle` | read | `GET /designer/product_orders/{product_order_id}/lifecycle` | which stage this product is in. | — |
|
|
197
200
|
| `makea-cli designer get product` | read | `GET /designer/get_product` | one product: status, documents, prices, links. | — |
|
|
198
201
|
| `makea-cli designer get production-price` | read | `GET /designer/production_orders/{production_order_id}/calculate_price` | what the deposit (or the balance, --phase final) comes to for the quantities on the run right now, line by line. | — |
|
|
@@ -235,7 +238,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
235
238
|
|---|---|---|---|---|
|
|
236
239
|
| `makea-cli supplier create note` | write(ask) | `POST /supplier/products/{id}/channels/{key}/notes` | post a message into a product channel you are part of. | — |
|
|
237
240
|
| `makea-cli supplier download document` | read | `GET /supplier/document/download` | save a document you are allowed to read. | — |
|
|
238
|
-
| `makea-cli supplier get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
241
|
+
| `makea-cli supplier get file` | read | local (makea_cli.file_reader) | read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine; saves PNG pictures of what it draws next to it. No API call. | — |
|
|
239
242
|
| `makea-cli supplier get pack` | read | `GET /supplier/quote_requests/{id}/technical-specifications` | the tech pack for an RFQ, as the factory is allowed to see it (`--readable`: labels, values and blank essentials only). | — |
|
|
240
243
|
| `makea-cli supplier get pack-changes` | read | `GET /supplier/products/{product_id}/specification-change-history` | what changed in the pack since you quoted, field by field. | — |
|
|
241
244
|
| `makea-cli supplier get profile` | read | `GET /supplier/profile` | your own supplier profile. | — |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.31"
|
|
@@ -1871,6 +1871,21 @@ def designer_create_proposals(product_id: str, proposals: list[dict[str, Any]])
|
|
|
1871
1871
|
)
|
|
1872
1872
|
|
|
1873
1873
|
|
|
1874
|
+
def designer_create_pack_file(product_id: str, body: dict[str, Any]) -> Any:
|
|
1875
|
+
"""POST /designer/products/{id}/tech_spec/files — put an uploaded file on a pack section."""
|
|
1876
|
+
return post_json(f"/designer/products/{product_id}/tech_spec/files", body)
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
def designer_library_schema() -> Any:
|
|
1880
|
+
"""GET /designer/library_components/schema — library sections and their field keys."""
|
|
1881
|
+
return get_json("/designer/library_components/schema")
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
def designer_create_library_component(body: dict[str, Any]) -> Any:
|
|
1885
|
+
"""POST /designer/library_components — {section, name?, values, document_ids?, draft?}."""
|
|
1886
|
+
return post_json("/designer/library_components", body)
|
|
1887
|
+
|
|
1888
|
+
|
|
1874
1889
|
def designer_bulk_accept_actions(channel_id: str, actions: list[dict[str, str]]) -> Any:
|
|
1875
1890
|
"""POST /designer/channels/{id}/suggested_actions/bulk_accept — accept several at once."""
|
|
1876
1891
|
return post_json(
|
|
@@ -750,6 +750,121 @@ def register(app: typer.Typer) -> None:
|
|
|
750
750
|
items = [{k: v for k, v in one.items() if v is not None}]
|
|
751
751
|
print_json_or_exit(console, lambda: api_client.designer_create_proposals(product, items))
|
|
752
752
|
|
|
753
|
+
@command(
|
|
754
|
+
app,
|
|
755
|
+
"designer",
|
|
756
|
+
"create",
|
|
757
|
+
"pack-file",
|
|
758
|
+
api="POST /designer/products/{product_id}/tech_spec/files",
|
|
759
|
+
side_effects=["adds the file to the tech pack section and to the product's documents"],
|
|
760
|
+
approval="auto",
|
|
761
|
+
help=(
|
|
762
|
+
"POST /designer/products/{id}/tech_spec/files — put a file the user uploaded (a "
|
|
763
|
+
"document_id) on a tech pack section. Pattern files (DXF, RUL, PLT, AAMA/ASTM) go "
|
|
764
|
+
"on Patterns, which is the default for them. Name the section as `get tech-pack "
|
|
765
|
+
'--readable` shows it ("Patterns", "Main fabric", "Additional notes"); pick a row '
|
|
766
|
+
"with --item or --entry. A refusal lists the sections that exist."
|
|
767
|
+
),
|
|
768
|
+
)
|
|
769
|
+
def create_pack_file(
|
|
770
|
+
product: str = typer.Option(..., "--product", "-p", help="Product id."),
|
|
771
|
+
document: list[str] = typer.Option(
|
|
772
|
+
..., "--document", "-d", help="document_id of the uploaded file (repeat)."
|
|
773
|
+
),
|
|
774
|
+
section: str | None = typer.Option(
|
|
775
|
+
None,
|
|
776
|
+
"--section",
|
|
777
|
+
help="Section, by label or key. Omit for a pattern file (goes on Patterns).",
|
|
778
|
+
),
|
|
779
|
+
entry: int | None = typer.Option(
|
|
780
|
+
None, "--entry", help="Row number in a repeating section, 0 = first."
|
|
781
|
+
),
|
|
782
|
+
item: str | None = typer.Option(
|
|
783
|
+
None, "--item", help='Pick the row that holds this value now (e.g. "silk crepe").'
|
|
784
|
+
),
|
|
785
|
+
) -> None:
|
|
786
|
+
"""Put uploaded files on a tech pack section."""
|
|
787
|
+
body: dict[str, Any] = {"document_ids": list(document)}
|
|
788
|
+
if section:
|
|
789
|
+
body["section"] = section
|
|
790
|
+
if entry is not None:
|
|
791
|
+
body["entry"] = entry
|
|
792
|
+
if item:
|
|
793
|
+
body["item"] = item
|
|
794
|
+
print_json_or_exit(console, lambda: api_client.designer_create_pack_file(product, body))
|
|
795
|
+
|
|
796
|
+
@command(
|
|
797
|
+
app,
|
|
798
|
+
"designer",
|
|
799
|
+
"get",
|
|
800
|
+
"library-schema",
|
|
801
|
+
api="GET /designer/library_components/schema",
|
|
802
|
+
help=(
|
|
803
|
+
"GET /designer/library_components/schema — the library's sections (fabrics, trims, "
|
|
804
|
+
"colours…) and, for each, its field keys, labels, options and which are required. "
|
|
805
|
+
"Read it before `create library-element`."
|
|
806
|
+
),
|
|
807
|
+
)
|
|
808
|
+
def get_library_schema() -> None:
|
|
809
|
+
"""Print the library sections and their fields."""
|
|
810
|
+
print_json_or_exit(console, api_client.designer_library_schema)
|
|
811
|
+
|
|
812
|
+
@command(
|
|
813
|
+
app,
|
|
814
|
+
"designer",
|
|
815
|
+
"create",
|
|
816
|
+
"library-element",
|
|
817
|
+
api="POST /designer/library_components",
|
|
818
|
+
side_effects=["adds the element to the brand's library, shared with their team"],
|
|
819
|
+
approval="auto",
|
|
820
|
+
help=(
|
|
821
|
+
"POST /designer/library_components — add a fabric, trim or other part to the brand's "
|
|
822
|
+
"library. --section and the --values keys come from `get library-schema`. --document "
|
|
823
|
+
"attaches an uploaded photo or data sheet. When required fields are unknown, pass "
|
|
824
|
+
"--draft: it is saved as a draft for the brand to finish."
|
|
825
|
+
),
|
|
826
|
+
)
|
|
827
|
+
def create_library_element(
|
|
828
|
+
section: str = typer.Option(..., "--section", "-s", help="Section key, e.g. MAIN_FABRIC."),
|
|
829
|
+
values: str | None = typer.Option(
|
|
830
|
+
None, "--values", help="JSON object of {field key: value} from get library-schema."
|
|
831
|
+
),
|
|
832
|
+
values_file: Path | None = typer.Option(
|
|
833
|
+
None,
|
|
834
|
+
"--values-file",
|
|
835
|
+
"-f",
|
|
836
|
+
exists=True,
|
|
837
|
+
dir_okay=False,
|
|
838
|
+
help="JSON file holding the values object (instead of --values).",
|
|
839
|
+
),
|
|
840
|
+
name: str | None = typer.Option(
|
|
841
|
+
None, "--name", "-n", help="Display name (defaults to the name field)."
|
|
842
|
+
),
|
|
843
|
+
document: list[str] = typer.Option(
|
|
844
|
+
[], "--document", "-d", help="document_id of a photo or data sheet (repeat)."
|
|
845
|
+
),
|
|
846
|
+
product: str | None = typer.Option(
|
|
847
|
+
None, "--product", "-p", help="The product it was made for, if any."
|
|
848
|
+
),
|
|
849
|
+
draft: bool = typer.Option(
|
|
850
|
+
False, "--draft", help="Save as a draft when required fields are still unknown."
|
|
851
|
+
),
|
|
852
|
+
) -> None:
|
|
853
|
+
"""Add an element to the brand's library."""
|
|
854
|
+
body: dict[str, Any] = {
|
|
855
|
+
"section": section,
|
|
856
|
+
"values": _json_object(values, values_file, "values"),
|
|
857
|
+
}
|
|
858
|
+
if name:
|
|
859
|
+
body["name"] = name
|
|
860
|
+
if document:
|
|
861
|
+
body["document_ids"] = list(document)
|
|
862
|
+
if product:
|
|
863
|
+
body["product_reference_id"] = product
|
|
864
|
+
if draft:
|
|
865
|
+
body["draft"] = True
|
|
866
|
+
print_json_or_exit(console, lambda: api_client.designer_create_library_component(body))
|
|
867
|
+
|
|
753
868
|
@command(
|
|
754
869
|
app,
|
|
755
870
|
"designer",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""``makea-cli <persona> get file PATH``: read a file a brand sent, locally.
|
|
2
2
|
|
|
3
|
-
Pattern DXF/RUL, Illustrator, EPS, Photoshop, embroidery (PES/DST/EMB), ZIP,
|
|
3
|
+
Pattern DXF/RUL/PLT, Illustrator, EPS, Photoshop, embroidery (PES/DST/EMB), ZIP,
|
|
4
4
|
AVIF/TIFF/SVG. No API call: the file never leaves the machine. Pictures of what
|
|
5
5
|
the file draws are saved next to it so the caller can look at them (an agent
|
|
6
6
|
reads them with its image tool).
|
|
@@ -21,7 +21,7 @@ from makea_cli.registry import command
|
|
|
21
21
|
console = Console()
|
|
22
22
|
|
|
23
23
|
_HELP = (
|
|
24
|
-
"Read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or "
|
|
24
|
+
"Read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or "
|
|
25
25
|
"AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws."
|
|
26
26
|
)
|
|
27
27
|
|
|
@@ -1071,6 +1071,261 @@ def _read_rul(data: bytes, name: str, depth: int) -> Reading:
|
|
|
1071
1071
|
return Reading("grade-rules", "RUL", " ".join(lines) + "\n\n```\n" + text[:6000] + "\n```")
|
|
1072
1072
|
|
|
1073
1073
|
|
|
1074
|
+
# ── Pattern: HPGL plot files (.plt) ──────────────────────────────────────
|
|
1075
|
+
#
|
|
1076
|
+
# What a pattern CAD sends to a cutting or marker plotter: pen-up / pen-down
|
|
1077
|
+
# moves in plotter units (40 to the mm), with piece names and sizes as LB text.
|
|
1078
|
+
# A plot has no pieces, only strokes, so it says what it draws and how big; the
|
|
1079
|
+
# piece table comes from the DXF.
|
|
1080
|
+
|
|
1081
|
+
_HPGL_EXTS = (".plt", ".hpgl", ".hpg", ".hgl")
|
|
1082
|
+
_HPGL_UNITS_PER_MM = 40.0
|
|
1083
|
+
# A full marker is millions of moves; past this the drawing is cut short (and
|
|
1084
|
+
# says so) so a read stays bounded in time and memory.
|
|
1085
|
+
_HPGL_MAX_POINTS = 2_000_000
|
|
1086
|
+
_HPGL_CMD = re.compile(rb"[A-Za-z]{2}")
|
|
1087
|
+
_HPGL_NUM = re.compile(rb"[-+]?(?:\d+\.?\d*|\.\d+)")
|
|
1088
|
+
# PCL / PJL wrapping around HP-GL/2: "ESC%-12345X@PJL ...", "ESC%1B", "ESCE".
|
|
1089
|
+
_PCL_ESCAPE = re.compile(rb"\x1b(?:%-12345X|%-?\d*[AB]|E|\*[a-z]\d*[A-Z])|@PJL[^\n]*\n?")
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
def _is_hpgl(head: bytes, ext: str) -> bool:
|
|
1093
|
+
if ext in _HPGL_EXTS:
|
|
1094
|
+
return True
|
|
1095
|
+
h = _PCL_ESCAPE.sub(b"", head).lstrip(b"\x00\r\n\t ;")
|
|
1096
|
+
return bool(re.match(rb"(?:IN|DF)\s*;\s*(?:[A-Z]{2}[^;]*;\s*){0,6}(?:SP|PU|PD|PA)", h))
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
@dataclass
|
|
1100
|
+
class _Plot:
|
|
1101
|
+
strokes: List[Tuple[int, List[Tuple[float, float]]]] = field(
|
|
1102
|
+
default_factory=list
|
|
1103
|
+
) # (pen, points)
|
|
1104
|
+
labels: List[str] = field(default_factory=list)
|
|
1105
|
+
raw_labels: List[bytes] = field(default_factory=list)
|
|
1106
|
+
points: int = 0
|
|
1107
|
+
truncated: bool = False
|
|
1108
|
+
scaled: bool = False
|
|
1109
|
+
pens: List[int] = field(default_factory=list)
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
_HPGL_ABS_POINTS = (b"EA", b"RA")
|
|
1113
|
+
_HPGL_REL_POINTS = (b"PR", b"ER", b"RR", b"AR")
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
def _hpgl_to_plotter(cmd: bytes, params: List[float], absolute: bool, t) -> List[float]:
|
|
1117
|
+
"""A command's coordinates from user units (after SC) to plotter units."""
|
|
1118
|
+
fx, fy, ox, oy = t
|
|
1119
|
+
if cmd == b"CI":
|
|
1120
|
+
return [params[0] * fx] + params[1:] if params else params
|
|
1121
|
+
if cmd in (b"AA", b"AR"):
|
|
1122
|
+
# Centre then sweep angle (degrees) and chord tolerance: only the
|
|
1123
|
+
# centre is a coordinate.
|
|
1124
|
+
head = _hpgl_to_plotter(b"PA" if cmd == b"AA" else b"PR", params[:2], True, t)
|
|
1125
|
+
return head + params[2:]
|
|
1126
|
+
is_abs = cmd == b"PA" or cmd in _HPGL_ABS_POINTS or (cmd in (b"PU", b"PD") and absolute)
|
|
1127
|
+
if not is_abs and cmd not in _HPGL_REL_POINTS + (b"PU", b"PD"):
|
|
1128
|
+
return params
|
|
1129
|
+
if is_abs:
|
|
1130
|
+
return [(ox + v * fx) if k % 2 == 0 else (oy + v * fy) for k, v in enumerate(params)]
|
|
1131
|
+
return [v * (fx if k % 2 == 0 else fy) for k, v in enumerate(params)]
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
def _parse_hpgl(data: bytes) -> _Plot:
|
|
1135
|
+
"""A small HP-GL / HP-GL/2 interpreter: PU PD PA PR, SP, LB (and DT),
|
|
1136
|
+
IP + SC scaling, CI and the rectangles. Anything else is skipped; arcs are
|
|
1137
|
+
joined as chords. Everything is kept in plotter units (40 to the mm)."""
|
|
1138
|
+
data = _PCL_ESCAPE.sub(b"", data)
|
|
1139
|
+
plot = _Plot()
|
|
1140
|
+
x = y = 0.0
|
|
1141
|
+
# User units -> plotter units: X = ox + x * fx. Identity until SC maps the
|
|
1142
|
+
# IP points (some CADs plot in 1/1000 inch: IP0,0,1016,1016;SC0,1000,0,1000).
|
|
1143
|
+
ip: Optional[Tuple[float, float, float, float]] = None
|
|
1144
|
+
fx = fy = 1.0
|
|
1145
|
+
ox = oy = 0.0
|
|
1146
|
+
absolute, pen_down, pen = True, False, 1
|
|
1147
|
+
cur: Optional[List[Tuple[float, float]]] = None
|
|
1148
|
+
label_end = b"\x03"
|
|
1149
|
+
n = len(data)
|
|
1150
|
+
i = 0
|
|
1151
|
+
|
|
1152
|
+
def start():
|
|
1153
|
+
nonlocal cur
|
|
1154
|
+
cur = [(x, y)]
|
|
1155
|
+
plot.strokes.append((pen, cur))
|
|
1156
|
+
|
|
1157
|
+
def move(nx: float, ny: float):
|
|
1158
|
+
nonlocal x, y, cur
|
|
1159
|
+
if plot.points >= _HPGL_MAX_POINTS:
|
|
1160
|
+
plot.truncated = True
|
|
1161
|
+
x, y = nx, ny
|
|
1162
|
+
return
|
|
1163
|
+
if pen_down:
|
|
1164
|
+
if cur is None:
|
|
1165
|
+
start()
|
|
1166
|
+
cur.append((nx, ny))
|
|
1167
|
+
plot.points += 1
|
|
1168
|
+
else:
|
|
1169
|
+
cur = None
|
|
1170
|
+
x, y = nx, ny
|
|
1171
|
+
|
|
1172
|
+
while i < n:
|
|
1173
|
+
m = _HPGL_CMD.search(data, i)
|
|
1174
|
+
if not m:
|
|
1175
|
+
break
|
|
1176
|
+
cmd = m.group().upper()
|
|
1177
|
+
i = m.end()
|
|
1178
|
+
if cmd == b"LB":
|
|
1179
|
+
end = data.find(label_end, i)
|
|
1180
|
+
end = n if end < 0 else end
|
|
1181
|
+
plot.raw_labels.append(data[i:end])
|
|
1182
|
+
i = end + len(label_end)
|
|
1183
|
+
continue
|
|
1184
|
+
if cmd == b"DT":
|
|
1185
|
+
j = i
|
|
1186
|
+
while j < n and data[j : j + 1] in (b" ", b"\t"):
|
|
1187
|
+
j += 1
|
|
1188
|
+
if j < n and data[j : j + 1] not in (b";",):
|
|
1189
|
+
label_end = data[j : j + 1]
|
|
1190
|
+
i = j + 1
|
|
1191
|
+
continue
|
|
1192
|
+
# Parameters run to the next command letter or ';'.
|
|
1193
|
+
j = i
|
|
1194
|
+
while j < n and not (data[j : j + 1].isalpha() or data[j : j + 1] == b";"):
|
|
1195
|
+
j += 1
|
|
1196
|
+
params = [float(v) for v in _HPGL_NUM.findall(data[i:j])]
|
|
1197
|
+
i = j + 1 if j < n and data[j : j + 1] == b";" else j
|
|
1198
|
+
if (fx, fy, ox, oy) != (1.0, 1.0, 0.0, 0.0):
|
|
1199
|
+
params = _hpgl_to_plotter(cmd, params, absolute, (fx, fy, ox, oy))
|
|
1200
|
+
if cmd in (b"PU", b"PD", b"PA", b"PR"):
|
|
1201
|
+
if cmd == b"PU":
|
|
1202
|
+
pen_down = False
|
|
1203
|
+
cur = None
|
|
1204
|
+
elif cmd == b"PD":
|
|
1205
|
+
pen_down = True
|
|
1206
|
+
elif cmd == b"PA":
|
|
1207
|
+
absolute = True
|
|
1208
|
+
else:
|
|
1209
|
+
absolute = False
|
|
1210
|
+
for k in range(0, len(params) - 1, 2):
|
|
1211
|
+
nx, ny = params[k], params[k + 1]
|
|
1212
|
+
move(nx, ny) if absolute else move(x + nx, y + ny)
|
|
1213
|
+
elif cmd == b"SP":
|
|
1214
|
+
pen = int(params[0]) if params else 0
|
|
1215
|
+
cur = None
|
|
1216
|
+
if pen and pen not in plot.pens:
|
|
1217
|
+
plot.pens.append(pen)
|
|
1218
|
+
elif cmd == b"CI" and params:
|
|
1219
|
+
r = params[0]
|
|
1220
|
+
cx, cy = x, y
|
|
1221
|
+
ring = [
|
|
1222
|
+
(cx + r * math.cos(t / 24 * 2 * math.pi), cy + r * math.sin(t / 24 * 2 * math.pi))
|
|
1223
|
+
for t in range(25)
|
|
1224
|
+
]
|
|
1225
|
+
plot.strokes.append((pen, ring))
|
|
1226
|
+
plot.points += len(ring)
|
|
1227
|
+
elif cmd in (b"EA", b"ER", b"RA", b"RR") and len(params) >= 2:
|
|
1228
|
+
x1, y1 = (
|
|
1229
|
+
(params[0], params[1]) if cmd in (b"EA", b"RA") else (x + params[0], y + params[1])
|
|
1230
|
+
)
|
|
1231
|
+
plot.strokes.append((pen, [(x, y), (x1, y), (x1, y1), (x, y1), (x, y)]))
|
|
1232
|
+
plot.points += 5
|
|
1233
|
+
elif cmd in (b"AA", b"AR") and len(params) >= 3:
|
|
1234
|
+
cx, cy = (params[0], params[1]) if cmd == b"AA" else (x + params[0], y + params[1])
|
|
1235
|
+
r = math.hypot(x - cx, y - cy)
|
|
1236
|
+
a0 = math.atan2(y - cy, x - cx)
|
|
1237
|
+
sweep = math.radians(params[2])
|
|
1238
|
+
steps = max(2, int(abs(params[2]) / 10))
|
|
1239
|
+
for k in range(1, steps + 1):
|
|
1240
|
+
a = a0 + sweep * k / steps
|
|
1241
|
+
move(cx + r * math.cos(a), cy + r * math.sin(a))
|
|
1242
|
+
elif cmd == b"IP":
|
|
1243
|
+
ip = tuple(params[:4]) if len(params) >= 4 else None
|
|
1244
|
+
elif cmd == b"SC":
|
|
1245
|
+
if len(params) >= 4 and ip and params[1] != params[0] and params[3] != params[2]:
|
|
1246
|
+
fx = (ip[2] - ip[0]) / (params[1] - params[0])
|
|
1247
|
+
fy = (ip[3] - ip[1]) / (params[3] - params[2])
|
|
1248
|
+
ox, oy = ip[0] - params[0] * fx, ip[1] - params[2] * fy
|
|
1249
|
+
elif len(params) >= 4:
|
|
1250
|
+
plot.scaled = True # scaled against the plotter's own P1/P2: size unknown
|
|
1251
|
+
else:
|
|
1252
|
+
fx = fy = 1.0
|
|
1253
|
+
ox = oy = 0.0
|
|
1254
|
+
elif cmd == b"IN":
|
|
1255
|
+
absolute, pen_down, cur = True, False, None
|
|
1256
|
+
ip, fx, fy, ox, oy = None, 1.0, 1.0, 0.0, 0.0
|
|
1257
|
+
plot.strokes = [(p, pts) for p, pts in plot.strokes if len(pts) > 1]
|
|
1258
|
+
# One encoding for every label, decided on all of them together: a
|
|
1259
|
+
# one-character GBK piece name (领, 前) is no evidence on its own and
|
|
1260
|
+
# reads as Latin or as valid UTF-8 nonsense.
|
|
1261
|
+
joined = _decode_text(b"\x00".join(plot.raw_labels))
|
|
1262
|
+
plot.labels = [t.replace("\r", " ").replace("\n", " ").strip() for t in joined.split("\x00")]
|
|
1263
|
+
plot.labels = [t for t in plot.labels if t]
|
|
1264
|
+
return plot
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
def _read_hpgl(data: bytes, name: str, depth: int) -> Reading:
|
|
1268
|
+
ext = os.path.splitext(name.lower())[1]
|
|
1269
|
+
plot = _parse_hpgl(data)
|
|
1270
|
+
if not plot.strokes:
|
|
1271
|
+
return Reading(
|
|
1272
|
+
"pattern",
|
|
1273
|
+
_format_label(ext),
|
|
1274
|
+
f"{_format_label(ext)} file with no HP-GL drawing in it; it may be a plot file in "
|
|
1275
|
+
"another plotter language. To have it read, send an AAMA/ASTM DXF + RUL export "
|
|
1276
|
+
"of the pattern, or a PDF of the pieces.",
|
|
1277
|
+
readable=False,
|
|
1278
|
+
)
|
|
1279
|
+
pts = [p for _pen, s in plot.strokes for p in s]
|
|
1280
|
+
x0, y0, x1, y1 = _bbox(pts)
|
|
1281
|
+
labels = list(dict.fromkeys(plot.labels))
|
|
1282
|
+
head = ["HP-GL plot file: a pattern or marker as sent to a cutting plotter."]
|
|
1283
|
+
if plot.scaled:
|
|
1284
|
+
head.append(
|
|
1285
|
+
f"It sets its own drawing scale, so its size is in its own units: "
|
|
1286
|
+
f"{_fmt(x1 - x0)} x {_fmt(y1 - y0)}."
|
|
1287
|
+
)
|
|
1288
|
+
else:
|
|
1289
|
+
w_cm = (x1 - x0) / _HPGL_UNITS_PER_MM / 10
|
|
1290
|
+
h_cm = (y1 - y0) / _HPGL_UNITS_PER_MM / 10
|
|
1291
|
+
head.append(f"It draws {_fmt(w_cm)} x {_fmt(h_cm)} cm (40 plotter units to the mm).")
|
|
1292
|
+
head.append(
|
|
1293
|
+
f"{len(plot.strokes)} line(s), {plot.points} point(s), pen(s) {', '.join(map(str, plot.pens)) or '1'}."
|
|
1294
|
+
)
|
|
1295
|
+
if plot.truncated:
|
|
1296
|
+
head.append(f"Only the first {_HPGL_MAX_POINTS} points were read.")
|
|
1297
|
+
body = " ".join(head)
|
|
1298
|
+
if labels:
|
|
1299
|
+
shown = labels[:80]
|
|
1300
|
+
body += "\n\nText in the plot (piece names, sizes and notes, in plot order):\n" + "\n".join(
|
|
1301
|
+
f"- {t}" for t in shown
|
|
1302
|
+
)
|
|
1303
|
+
if len(labels) > len(shown):
|
|
1304
|
+
body += f"\n- … and {len(labels) - len(shown)} more"
|
|
1305
|
+
body += (
|
|
1306
|
+
"\n\nA plot file has no pattern pieces, only strokes, so piece sizes, seam "
|
|
1307
|
+
"allowance and grading are not stated; those come from the DXF + RUL."
|
|
1308
|
+
)
|
|
1309
|
+
return Reading("pattern", _format_label(ext), body, _draw_hpgl(plot, (x0, y0, x1, y1)))
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
def _draw_hpgl(plot: _Plot, bb: Tuple[float, float, float, float]) -> List[Picture]:
|
|
1313
|
+
Image = _pil()
|
|
1314
|
+
if not Image:
|
|
1315
|
+
return []
|
|
1316
|
+
from PIL import ImageDraw # noqa: PLC0415
|
|
1317
|
+
|
|
1318
|
+
w, h = max(bb[2] - bb[0], 1e-6), max(bb[3] - bb[1], 1e-6)
|
|
1319
|
+
scale = (_PICTURE_SIDE - 40) / max(w, h)
|
|
1320
|
+
im = Image.new("RGB", (max(1, int(w * scale) + 40), max(1, int(h * scale) + 40)), "white")
|
|
1321
|
+
draw = ImageDraw.Draw(im)
|
|
1322
|
+
colours = ["#111111", "#d0021b", "#4a90e2", "#2e7d32", "#9b9b9b", "#5a12c4", "#e68a00"]
|
|
1323
|
+
for pen, pts in plot.strokes:
|
|
1324
|
+
xy = [(20 + (px - bb[0]) * scale, 20 + (bb[3] - py) * scale) for px, py in pts]
|
|
1325
|
+
draw.line(xy, fill=colours[(max(pen, 1) - 1) % len(colours)], width=2)
|
|
1326
|
+
return [Picture("plot (each pen in its own colour)", _png(im))]
|
|
1327
|
+
|
|
1328
|
+
|
|
1074
1329
|
# ── Archives ─────────────────────────────────────────────────────────────
|
|
1075
1330
|
|
|
1076
1331
|
|
|
@@ -1266,6 +1521,7 @@ _READERS: List[Tuple[Callable[[bytes, str], bool], Callable[[bytes, str, int], R
|
|
|
1266
1521
|
(_is_stitch, _read_stitch),
|
|
1267
1522
|
(_is_rul, _read_rul),
|
|
1268
1523
|
(_is_dxf, _read_dxf),
|
|
1524
|
+
(_is_hpgl, _read_hpgl),
|
|
1269
1525
|
(_is_zip, _read_zip),
|
|
1270
1526
|
(_is_svg, _read_svg),
|
|
1271
1527
|
(_is_image, _read_image),
|
|
@@ -2483,7 +2483,7 @@
|
|
|
2483
2483
|
"idempotent": null,
|
|
2484
2484
|
"limit": null,
|
|
2485
2485
|
"legacy": [],
|
|
2486
|
-
"help": "Read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws.",
|
|
2486
|
+
"help": "Read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws.",
|
|
2487
2487
|
"function": "makea_cli.commands.files.cmd._register_for.<locals>.get_file",
|
|
2488
2488
|
"flags": [
|
|
2489
2489
|
{
|
|
@@ -8290,6 +8290,116 @@
|
|
|
8290
8290
|
}
|
|
8291
8291
|
]
|
|
8292
8292
|
},
|
|
8293
|
+
{
|
|
8294
|
+
"name": "designer create library-element",
|
|
8295
|
+
"persona": "designer",
|
|
8296
|
+
"verb": "create",
|
|
8297
|
+
"noun": "library-element",
|
|
8298
|
+
"access": "write",
|
|
8299
|
+
"api": "POST /designer/library_components",
|
|
8300
|
+
"external": null,
|
|
8301
|
+
"side_effects": [
|
|
8302
|
+
"adds the element to the brand's library, shared with their team"
|
|
8303
|
+
],
|
|
8304
|
+
"approval": "auto",
|
|
8305
|
+
"reversible": null,
|
|
8306
|
+
"idempotent": null,
|
|
8307
|
+
"limit": null,
|
|
8308
|
+
"legacy": [],
|
|
8309
|
+
"help": "POST /designer/library_components — add a fabric, trim or other part to the brand's library. --section and the --values keys come from `get library-schema`. --document attaches an uploaded photo or data sheet. When required fields are unknown, pass --draft: it is saved as a draft for the brand to finish.",
|
|
8310
|
+
"function": "makea_cli.commands.brand.cmd.register.<locals>.create_library_element",
|
|
8311
|
+
"flags": [
|
|
8312
|
+
{
|
|
8313
|
+
"name": "section",
|
|
8314
|
+
"kind": "option",
|
|
8315
|
+
"opts": [
|
|
8316
|
+
"--section",
|
|
8317
|
+
"-s"
|
|
8318
|
+
],
|
|
8319
|
+
"type": "text",
|
|
8320
|
+
"required": true,
|
|
8321
|
+
"multiple": false,
|
|
8322
|
+
"default": null,
|
|
8323
|
+
"help": "Section key, e.g. MAIN_FABRIC."
|
|
8324
|
+
},
|
|
8325
|
+
{
|
|
8326
|
+
"name": "values",
|
|
8327
|
+
"kind": "option",
|
|
8328
|
+
"opts": [
|
|
8329
|
+
"--values"
|
|
8330
|
+
],
|
|
8331
|
+
"type": "text",
|
|
8332
|
+
"required": false,
|
|
8333
|
+
"multiple": false,
|
|
8334
|
+
"default": null,
|
|
8335
|
+
"help": "JSON object of {field key: value} from get library-schema."
|
|
8336
|
+
},
|
|
8337
|
+
{
|
|
8338
|
+
"name": "values_file",
|
|
8339
|
+
"kind": "option",
|
|
8340
|
+
"opts": [
|
|
8341
|
+
"--values-file",
|
|
8342
|
+
"-f"
|
|
8343
|
+
],
|
|
8344
|
+
"type": "file",
|
|
8345
|
+
"required": false,
|
|
8346
|
+
"multiple": false,
|
|
8347
|
+
"default": null,
|
|
8348
|
+
"help": "JSON file holding the values object (instead of --values)."
|
|
8349
|
+
},
|
|
8350
|
+
{
|
|
8351
|
+
"name": "name",
|
|
8352
|
+
"kind": "option",
|
|
8353
|
+
"opts": [
|
|
8354
|
+
"--name",
|
|
8355
|
+
"-n"
|
|
8356
|
+
],
|
|
8357
|
+
"type": "text",
|
|
8358
|
+
"required": false,
|
|
8359
|
+
"multiple": false,
|
|
8360
|
+
"default": null,
|
|
8361
|
+
"help": "Display name (defaults to the name field)."
|
|
8362
|
+
},
|
|
8363
|
+
{
|
|
8364
|
+
"name": "document",
|
|
8365
|
+
"kind": "option",
|
|
8366
|
+
"opts": [
|
|
8367
|
+
"--document",
|
|
8368
|
+
"-d"
|
|
8369
|
+
],
|
|
8370
|
+
"type": "text",
|
|
8371
|
+
"required": false,
|
|
8372
|
+
"multiple": true,
|
|
8373
|
+
"default": [],
|
|
8374
|
+
"help": "document_id of a photo or data sheet (repeat)."
|
|
8375
|
+
},
|
|
8376
|
+
{
|
|
8377
|
+
"name": "product",
|
|
8378
|
+
"kind": "option",
|
|
8379
|
+
"opts": [
|
|
8380
|
+
"--product",
|
|
8381
|
+
"-p"
|
|
8382
|
+
],
|
|
8383
|
+
"type": "text",
|
|
8384
|
+
"required": false,
|
|
8385
|
+
"multiple": false,
|
|
8386
|
+
"default": null,
|
|
8387
|
+
"help": "The product it was made for, if any."
|
|
8388
|
+
},
|
|
8389
|
+
{
|
|
8390
|
+
"name": "draft",
|
|
8391
|
+
"kind": "option",
|
|
8392
|
+
"opts": [
|
|
8393
|
+
"--draft"
|
|
8394
|
+
],
|
|
8395
|
+
"type": "flag",
|
|
8396
|
+
"required": false,
|
|
8397
|
+
"multiple": false,
|
|
8398
|
+
"default": false,
|
|
8399
|
+
"help": "Save as a draft when required fields are still unknown."
|
|
8400
|
+
}
|
|
8401
|
+
]
|
|
8402
|
+
},
|
|
8293
8403
|
{
|
|
8294
8404
|
"name": "designer create note",
|
|
8295
8405
|
"persona": "designer",
|
|
@@ -8348,6 +8458,89 @@
|
|
|
8348
8458
|
}
|
|
8349
8459
|
]
|
|
8350
8460
|
},
|
|
8461
|
+
{
|
|
8462
|
+
"name": "designer create pack-file",
|
|
8463
|
+
"persona": "designer",
|
|
8464
|
+
"verb": "create",
|
|
8465
|
+
"noun": "pack-file",
|
|
8466
|
+
"access": "write",
|
|
8467
|
+
"api": "POST /designer/products/{product_id}/tech_spec/files",
|
|
8468
|
+
"external": null,
|
|
8469
|
+
"side_effects": [
|
|
8470
|
+
"adds the file to the tech pack section and to the product's documents"
|
|
8471
|
+
],
|
|
8472
|
+
"approval": "auto",
|
|
8473
|
+
"reversible": null,
|
|
8474
|
+
"idempotent": null,
|
|
8475
|
+
"limit": null,
|
|
8476
|
+
"legacy": [],
|
|
8477
|
+
"help": "POST /designer/products/{id}/tech_spec/files — put a file the user uploaded (a document_id) on a tech pack section. Pattern files (DXF, RUL, PLT, AAMA/ASTM) go on Patterns, which is the default for them. Name the section as `get tech-pack --readable` shows it (\"Patterns\", \"Main fabric\", \"Additional notes\"); pick a row with --item or --entry. A refusal lists the sections that exist.",
|
|
8478
|
+
"function": "makea_cli.commands.brand.cmd.register.<locals>.create_pack_file",
|
|
8479
|
+
"flags": [
|
|
8480
|
+
{
|
|
8481
|
+
"name": "product",
|
|
8482
|
+
"kind": "option",
|
|
8483
|
+
"opts": [
|
|
8484
|
+
"--product",
|
|
8485
|
+
"-p"
|
|
8486
|
+
],
|
|
8487
|
+
"type": "text",
|
|
8488
|
+
"required": true,
|
|
8489
|
+
"multiple": false,
|
|
8490
|
+
"default": null,
|
|
8491
|
+
"help": "Product id."
|
|
8492
|
+
},
|
|
8493
|
+
{
|
|
8494
|
+
"name": "document",
|
|
8495
|
+
"kind": "option",
|
|
8496
|
+
"opts": [
|
|
8497
|
+
"--document",
|
|
8498
|
+
"-d"
|
|
8499
|
+
],
|
|
8500
|
+
"type": "text",
|
|
8501
|
+
"required": true,
|
|
8502
|
+
"multiple": true,
|
|
8503
|
+
"default": null,
|
|
8504
|
+
"help": "document_id of the uploaded file (repeat)."
|
|
8505
|
+
},
|
|
8506
|
+
{
|
|
8507
|
+
"name": "section",
|
|
8508
|
+
"kind": "option",
|
|
8509
|
+
"opts": [
|
|
8510
|
+
"--section"
|
|
8511
|
+
],
|
|
8512
|
+
"type": "text",
|
|
8513
|
+
"required": false,
|
|
8514
|
+
"multiple": false,
|
|
8515
|
+
"default": null,
|
|
8516
|
+
"help": "Section, by label or key. Omit for a pattern file (goes on Patterns)."
|
|
8517
|
+
},
|
|
8518
|
+
{
|
|
8519
|
+
"name": "entry",
|
|
8520
|
+
"kind": "option",
|
|
8521
|
+
"opts": [
|
|
8522
|
+
"--entry"
|
|
8523
|
+
],
|
|
8524
|
+
"type": "integer",
|
|
8525
|
+
"required": false,
|
|
8526
|
+
"multiple": false,
|
|
8527
|
+
"default": null,
|
|
8528
|
+
"help": "Row number in a repeating section, 0 = first."
|
|
8529
|
+
},
|
|
8530
|
+
{
|
|
8531
|
+
"name": "item",
|
|
8532
|
+
"kind": "option",
|
|
8533
|
+
"opts": [
|
|
8534
|
+
"--item"
|
|
8535
|
+
],
|
|
8536
|
+
"type": "text",
|
|
8537
|
+
"required": false,
|
|
8538
|
+
"multiple": false,
|
|
8539
|
+
"default": null,
|
|
8540
|
+
"help": "Pick the row that holds this value now (e.g. \"silk crepe\")."
|
|
8541
|
+
}
|
|
8542
|
+
]
|
|
8543
|
+
},
|
|
8351
8544
|
{
|
|
8352
8545
|
"name": "designer create product",
|
|
8353
8546
|
"persona": "designer",
|
|
@@ -9017,7 +9210,7 @@
|
|
|
9017
9210
|
"idempotent": null,
|
|
9018
9211
|
"limit": null,
|
|
9019
9212
|
"legacy": [],
|
|
9020
|
-
"help": "Read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws.",
|
|
9213
|
+
"help": "Read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws.",
|
|
9021
9214
|
"function": "makea_cli.commands.files.cmd._register_for.<locals>.get_file",
|
|
9022
9215
|
"flags": [
|
|
9023
9216
|
{
|
|
@@ -9046,6 +9239,24 @@
|
|
|
9046
9239
|
}
|
|
9047
9240
|
]
|
|
9048
9241
|
},
|
|
9242
|
+
{
|
|
9243
|
+
"name": "designer get library-schema",
|
|
9244
|
+
"persona": "designer",
|
|
9245
|
+
"verb": "get",
|
|
9246
|
+
"noun": "library-schema",
|
|
9247
|
+
"access": "read",
|
|
9248
|
+
"api": "GET /designer/library_components/schema",
|
|
9249
|
+
"external": null,
|
|
9250
|
+
"side_effects": [],
|
|
9251
|
+
"approval": "auto",
|
|
9252
|
+
"reversible": null,
|
|
9253
|
+
"idempotent": null,
|
|
9254
|
+
"limit": null,
|
|
9255
|
+
"legacy": [],
|
|
9256
|
+
"help": "GET /designer/library_components/schema — the library's sections (fabrics, trims, colours…) and, for each, its field keys, labels, options and which are required. Read it before `create library-element`.",
|
|
9257
|
+
"function": "makea_cli.commands.brand.cmd.register.<locals>.get_library_schema",
|
|
9258
|
+
"flags": []
|
|
9259
|
+
},
|
|
9049
9260
|
{
|
|
9050
9261
|
"name": "designer get lifecycle",
|
|
9051
9262
|
"persona": "designer",
|
|
@@ -10871,7 +11082,7 @@
|
|
|
10871
11082
|
"idempotent": null,
|
|
10872
11083
|
"limit": null,
|
|
10873
11084
|
"legacy": [],
|
|
10874
|
-
"help": "Read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws.",
|
|
11085
|
+
"help": "Read a pattern (DXF/RUL/PLT), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws.",
|
|
10875
11086
|
"function": "makea_cli.commands.files.cmd._register_for.<locals>.get_file",
|
|
10876
11087
|
"flags": [
|
|
10877
11088
|
{
|
|
@@ -100,6 +100,72 @@ def _palette_alpha_tiff() -> bytes:
|
|
|
100
100
|
return bytes(raw)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def _plt() -> bytes:
|
|
104
|
+
"""What a Chinese pattern CAD plots: GBK labels, one piece per pen-down run."""
|
|
105
|
+
body = (
|
|
106
|
+
"IN;SP1;PU0,0;PD4000,0,4000,8000,0,8000,0,0;" # 10 x 20 cm piece
|
|
107
|
+
"PU400,400;LB前片 M\x03;"
|
|
108
|
+
"SP2;PU2000,400;PD2000,7600;" # grain line, pen 2
|
|
109
|
+
"SP1;PU6000,0;PR;PD4000,0,0,4000,-4000,0,0,-4000;PA;" # relative square
|
|
110
|
+
"PU6400,400;LB后片 M\x03;PU0,0;SP0;"
|
|
111
|
+
)
|
|
112
|
+
return body.encode("gbk")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class HpglPlotTest(unittest.TestCase):
|
|
116
|
+
def test_reads_size_labels_and_draws(self):
|
|
117
|
+
r = read_bytes(_plt(), "dress.plt")
|
|
118
|
+
self.assertEqual((r.kind, r.format, r.readable), ("pattern", "PLT", True))
|
|
119
|
+
self.assertIn("25.0 x 20.0 cm", r.text)
|
|
120
|
+
self.assertIn("- 前片 M", r.text)
|
|
121
|
+
self.assertIn("- 后片 M", r.text)
|
|
122
|
+
self.assertIn("pen(s) 1, 2", r.text)
|
|
123
|
+
self.assertEqual(len(r.pictures), 1)
|
|
124
|
+
self.assertTrue(r.pictures[0].png.startswith(b"\x89PNG"))
|
|
125
|
+
|
|
126
|
+
def test_pcl_wrapped_hpgl2_without_extension(self):
|
|
127
|
+
data = b"\x1b%-12345X@PJL ENTER LANGUAGE=HPGL2\r\n\x1bE\x1b%1B" + _plt() + b"\x1b%0A\x1bE"
|
|
128
|
+
r = read_bytes(data, "plot")
|
|
129
|
+
self.assertEqual(r.kind, "pattern")
|
|
130
|
+
self.assertIn("25.0 x 20.0 cm", r.text)
|
|
131
|
+
|
|
132
|
+
def test_relative_moves(self):
|
|
133
|
+
r = read_bytes(b"IN;PA;PU0,0;PR;PD400,0;PD0,400;", "a.plt")
|
|
134
|
+
self.assertIn("1.0 x 1.0 cm", r.text)
|
|
135
|
+
|
|
136
|
+
def test_ip_sc_scale_in_thousandths_of_an_inch(self):
|
|
137
|
+
# A 72-inch marker plotted in 1/1000 inch (a real garment CAD export).
|
|
138
|
+
body = b"IN;IP0,0,1016,1016;SC0,1000,0,1000;SP1;PU0,0;PD10000,0,10000,72000,0,72000,0,0;PU;"
|
|
139
|
+
r = read_bytes(body, "72-MLXL2.PLT")
|
|
140
|
+
self.assertIn("25.4 x 183 cm", r.text)
|
|
141
|
+
rel = read_bytes(
|
|
142
|
+
b"IN;IP0,0,1016,1016;SC0,1000,0,1000;PU0,0;PR;PD1000,0;PA;PD1000,1000;", "a.plt"
|
|
143
|
+
)
|
|
144
|
+
self.assertIn("2.5 x 2.5 cm", rel.text)
|
|
145
|
+
|
|
146
|
+
def test_own_scale_is_not_called_cm(self):
|
|
147
|
+
r = read_bytes(b"IN;SC0,100,0,100;PU0,0;PD50,0,50,50;", "a.plt")
|
|
148
|
+
self.assertIn("its own units", r.text)
|
|
149
|
+
self.assertNotIn(" cm ", r.text)
|
|
150
|
+
|
|
151
|
+
def test_plt_in_another_plotter_language(self):
|
|
152
|
+
r = read_bytes(bytes(range(256)) * 4, "cutter.plt")
|
|
153
|
+
self.assertFalse(r.readable)
|
|
154
|
+
self.assertIn("DXF", r.text)
|
|
155
|
+
|
|
156
|
+
def test_one_character_gbk_names_decode_with_the_rest(self):
|
|
157
|
+
body = "IN;PU0,0;PD400,0,400,400;LB领\x03;LB前\x03;LB后裤片\x03;"
|
|
158
|
+
r = read_bytes(body.encode("gbk"), "pants.plt")
|
|
159
|
+
self.assertIn("- 领", r.text)
|
|
160
|
+
self.assertIn("- 前", r.text)
|
|
161
|
+
self.assertIn("- 后裤片", r.text)
|
|
162
|
+
|
|
163
|
+
def test_semicolonless_space_separated(self):
|
|
164
|
+
r = read_bytes(b"IN SP1 PU 0 0 PD 400 0 400 400 PU", "a.hpgl")
|
|
165
|
+
self.assertEqual(r.kind, "pattern")
|
|
166
|
+
self.assertIn("1.0 x 1.0 cm", r.text)
|
|
167
|
+
|
|
168
|
+
|
|
103
169
|
class EpsTest(unittest.TestCase):
|
|
104
170
|
def test_illustrator_preview_opens(self):
|
|
105
171
|
tiff = _palette_alpha_tiff()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.29"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|