diff --git a/SKILL.md b/SKILL.md index 66ff7c9..c079676 100644 --- a/SKILL.md +++ b/SKILL.md @@ -286,25 +286,11 @@ python scripts/fill_templates.py part.json \ --template assets/template/template.xlsx --dest // --design design.json ``` -Once you have the symbol and its Library Ref, you can also write the symbol's Altium parameters -onto the `.SchLib`. **House rule: the symbol carries _every_ parameter the workbook was filled -with from the datasheet — not just the fixed SOP set.** Build the parameter set straight from the -finished `.xlsx` and write it in: - -```bash -python scripts/schlib_params_from_xlsx.py --xlsx //.xlsx \ - --component --set "Process=" --set "Datasheet=" \ - --out params.json -python scripts/schlib_write.py --schlib .SchLib --params params.json --out //.SchLib -``` - -`schlib_params_from_xlsx.py` keeps every filled datasheet column (exact header as the parameter -name), drops the identity/version/model-link columns, renames `Rohs compliance → ROHS` and -`Operating Temp(°C) → Operating Temperature`, and merges in the SOP-only fields (`Manufacturer -Part` = MPN automatically; `Process` / `Datasheet` / `Value` / `Vecmocon Part Code` via `--set`). -See *Mandatory symbol parameters* below and `references/schlib_parameters.md` for the full method -(incl. the ANSI glyph transliteration, e.g. `Ω → Ohm`). This is how the datasheet values land in -the Altium symbol's properties. +Once you have the symbol and its Library Ref, you can also produce the **mandatory symbol +parameters** the SOP requires on the `.SchLib` (Manufacturer, Manufacturer Part, Value, +Tolerance, Operating Temperature, RoHS, Datasheet, Process, Vecmocon Part Code, …) — see +*Mandatory symbol parameters* below. This is optional per run but is how the datasheet values +land in the Altium symbol's properties. ### 7. Assemble the part folder @@ -327,11 +313,8 @@ user where it landed. The SOP (§5) requires every schematic symbol to carry a fixed parameter set in its Altium properties — `Manufacturer`, `Manufacturer Part`, `Value`, `Tolerance`, `Operating Temperature`, `ROHS`, `Datasheet`, `Process`, `Vecmocon Part Code`, and the two second-source -fields — with the **Comment** set to the MPN. **On top of that fixed minimum, the symbol also -carries every other parameter the part's workbook was filled with from the datasheet** (e.g. a -CMC's `Rated Current(A)`, `DC Resistance(mΩ)`, `Package`, `ESD Withstand Voltage(kV)`): the -Altium properties mirror the `.xlsx` row. The skill can stamp all of these onto the symbol -from the workbook + datasheet. +fields — with the **Comment** set to the MPN. The skill can stamp these onto the symbol from +the datasheet. This runs **as its own task too**, not only inside new-part creation: whenever the user hands over one or more `.SchLib` files and wants their parameters filled/updated (e.g. "\schlib", "add @@ -357,19 +340,16 @@ Leave any genuinely-unknown field blank — the SOP hides blank parameters, so a empty until filled. The full method for the second-source search is in `references/schlib_parameters.md`. -Build the `params.json` from the finished workbook (so it carries all the filled datasheet -parameters), then write it into the symbol: +Write the **full parameter set** — the typeid template's engineering columns **plus** the SOP +params above (see `references/schlib_parameters.md`). Collect your filled values into a +`params.json` and pass `--typeid` so the writer guarantees every template column is present +(blank where the datasheet is silent): ```bash -python scripts/schlib_params_from_xlsx.py --xlsx //.xlsx \ - --component --set "Process=" --set "Datasheet=" \ - [--set "Value="] [--set "Vecmocon Part Code="] --out params.json -python scripts/schlib_write.py --schlib .SchLib --params params.json --out //.SchLib +python scripts/schlib_write.py --schlib .SchLib --params params.json \ + --out //.SchLib --typeid ``` -(For a bare `.SchLib`-only task with no workbook, you can still hand-write `params.json` — the -shape is in `references/schlib_parameters.md`.) - Deliver the resulting `.SchLib`; the engineer opens it in Altium once to confirm it loads, then **Saves to Server** with a revision note. The full parameter set, each value's source, the `params.json` shape (incl. the `remove` list), and the mini-stream size caveat are in @@ -378,6 +358,24 @@ round-trip, fall back to `scripts/altium_params.py` (emits an Altium DXP script parameters from inside Altium). Always have the engineer confirm the file opens in Altium — the skill writes Altium's own binary format, so Altium is the final validator. +## Submitting to Altium 365 as Part Requests (web) + +If the org's central library is a managed **Altium 365 Workspace** (not the Gitea repos), the +skill's end task can submit each finished component as a **Part Request** through the Workspace +web UI, using browser automation (Claude-in-Chrome) in the operator's own signed-in Chrome — no +API token, no admin rights. A librarian then approves each request into the library. + +This runs over **every component processed in the run**: the skill writes a `part_requests.json` +manifest (one entry per component — manufacturer, MPN, Description, component type, the full +parameter set, and the local paths to its `.SchLib`/`.PcbLib`/datasheet), then the browser step +loops it, filling and submitting the form for each. On the **first** component, fill everything +and stop at Save for the operator to review; once confirmed, Save and loop the rest, logging each +Request Id. Full field mapping, prerequisites, and the exact browser steps are in +`references/part_request_web.md` — read it before driving the browser. + +Because the files upload from local disk, commit each component's `.SchLib`/`.PcbLib`/datasheet +to the operator's machine (device bridge) first, and put those local paths in the manifest. + ## Per-typeid versioning Versioning is **per typeid**, not global. Each typeid carries its own `template_version` and @@ -537,6 +535,9 @@ plain flat push, but it does not merge the changelog or blank the token, so pref - `references/schlib_parameters.md` — the SOP **mandatory symbol parameters** (§5) for the `.SchLib`: the parameter set, where each value comes from, and how the generated Altium script stamps them onto the symbol. +- `references/part_request_web.md` — submitting finished components to a managed Altium 365 + Workspace as **Part Requests** via browser automation (no token/admin): prerequisites, the + per-component field mapping, the `part_requests.json` manifest, and the looped browser steps. - `assets/template/versions.json` — per-typeid `template_version` + `skill_version`. - `assets/CHANGELOG.xlsx` — global version/parameter changelog (created on first add; merged into the skill repo's copy in Gitea by `push-skill`). @@ -547,14 +548,9 @@ plain flat push, but it does not merge the changelog or blank the token, so pref - `scripts/append_parameter.py` — append parameter(s) to a typeid, bump its versions, write the changelog. - `scripts/altium_refs.py` — read Library/Footprint Ref from `.SchLib`/`.PcbLib`. -- `scripts/schlib_params_from_xlsx.py` — build the symbol `params.json` from the finished - per-part `.xlsx`, so the `.SchLib` carries **every filled datasheet parameter** (not just - the SOP minimum); skips identity/version/model-link columns, renames a couple to their SOP - names, and merges in the SOP-only fields (`Manufacturer Part`, `Process`, `Datasheet`, …). -- `scripts/schlib_write.py` — write the parameters **directly into a `.SchLib`** (pure-Python OLE - rebuild; removes the Ultra-Librarian `Manufacturer_Name` / `Manufacturer_Part_Number` defaults; - transliterates non-ANSI unit glyphs, e.g. `Ω → Ohm`). Primary path; see - `references/schlib_parameters.md`. +- `scripts/schlib_write.py` — write the SOP mandatory parameters **directly into a `.SchLib`** + (pure-Python OLE rebuild; removes the Ultra-Librarian `Manufacturer_Name` / + `Manufacturer_Part_Number` defaults). Primary path; see `references/schlib_parameters.md`. - `scripts/altium_params.py` — fallback: generate an Altium DelphiScript that stamps the same parameters onto a `.SchLib` from inside Altium (DXP → Run Script). - `scripts/gitea_components.py` — `check-mpn`, `find-part` (locate an existing part to diff --git a/assets/CHANGELOG.xlsx b/assets/CHANGELOG.xlsx index 6cb33f5..3b95331 100644 Binary files a/assets/CHANGELOG.xlsx and b/assets/CHANGELOG.xlsx differ diff --git a/assets/template/template.xlsx b/assets/template/template.xlsx index ab04474..8292a39 100644 Binary files a/assets/template/template.xlsx and b/assets/template/template.xlsx differ diff --git a/assets/template/versions.json b/assets/template/versions.json index fb110ec..b29a5f1 100644 --- a/assets/template/versions.json +++ b/assets/template/versions.json @@ -88,8 +88,8 @@ "template_version": 1 }, "CMC": { - "skill_version": 2, - "template_version": 2 + "skill_version": 1, + "template_version": 1 }, "CMP": { "skill_version": 1, diff --git a/references/part_request_web.md b/references/part_request_web.md new file mode 100644 index 0000000..c8469d1 --- /dev/null +++ b/references/part_request_web.md @@ -0,0 +1,82 @@ +# Submitting components as Altium 365 Part Requests (web browser) + +When the central library is a managed **Altium 365 Workspace** and the operator is **not an +admin**, the skill's end task can be to submit each finished component as a **Part Request** +through the Workspace's web UI, driven by browser automation (Claude-in-Chrome). A librarian +then approves each request into the managed library. This runs in the operator's **own Chrome, +using their existing Altium 365 login**, so it needs **no API token and no admin rights** — it +just does what the engineer would do by hand, for every component in the run. + +This is the browser alternative to the headless API push (`altium365_push.py`, if a token is +ever available) and to the Gitea push. Use whichever matches how the org consumes the library. + +## Prerequisites (each run) + +- **Chrome open** with the Claude-in-Chrome extension enabled, and **site permission granted** + for the Workspace domain (e.g. `vecmocon.altium365.com`). +- **Signed in** to the Altium 365 Workspace as any member with rights to create Part Requests. +- **The component files on the LOCAL machine** — the browser uploads attachments from local + disk, so each component's `.SchLib` (with parameters written), `.PcbLib`, and datasheet must + exist on the operator's computer. If the skill produced them in the cloud, commit them to the + device first (device bridge) into a known folder, and put those local paths in the manifest. +- **The manifest** of components to submit this run (below). +- **Decisions the operator confirms once**: the default **Assign to** (the librarian/group), the + **Component Type** mapping for each typeid, and an optional **Required By** date. + +## Per-component field mapping (the Part Request form) + +| Form field | Value (from the skill's per-part data) | +|---|---| +| Manufacturer | `Manufacturer` param (e.g. `Taiyo Yuden`) | +| Manufacturer Part Numbers | `Manufacturer Part` param (the MPN) | +| Description | `Description` param (SOP format, e.g. `CHIP_CAP_1uF_6.3v_±10%_0402_x5r`) | +| Component Type | the typeid mapped to the Workspace's matching component type (confirm once per typeid) | +| State | leave `Opened: New` | +| Required By Date | optional org default | +| Assign to | the configured librarian / group | +| Parameters → Add | every parameter from the full set (name + value) | +| Attachments | the `.SchLib`, `.PcbLib`, and datasheet files | +| Parts List → Add | optional: add the MPN as a part choice so the librarian's mapping is pre-seeded | + +## The manifest + +The skill writes one `part_requests.json` per run listing every component it processed, so the +browser step can loop without re-deriving anything: + +```json +{"requests":[ + {"manufacturer":"Taiyo Yuden","mpn":"JMK105BJ105KV-F", + "description":"CHIP_CAP_1uF_6.3v_±10%_0402_x5r","component_type":"Capacitor", + "parameters":{"Value":"1u","Voltage(V)":"6.3","Tolerance":"±10%","...":"..."}, + "files":["C:\\...\\JMK105BJ105KV-F.SchLib","C:\\...\\.PcbLib","C:\\...\\.pdf"], + "assignee":"","required_by":""} +]} +``` + +Build it from each component's `params.json` plus the local file paths (after committing files +to the device). + +## Browser procedure (looped per component) + +Start the browser session with `tabs_context_mcp`, then for each entry in the manifest: + +1. Navigate to **Library → Part Requests → new request**. +2. Fill **Manufacturer**, **Manufacturer Part Numbers**, **Description**. +3. Choose **Component Type**; set **Assign to**; optional **Required By Date**. Leave State as + `Opened: New`. +4. **Parameters → Add**: add each parameter name + value. +5. **Attachments** (Choose file / drop): upload the `.SchLib`, `.PcbLib`, and datasheet. +6. Review, then **Save**. Record the auto-assigned **Request Id**. +7. Move to the next entry. + +## Safety and auditing + +- On the **first component of a run**, fill everything and **stop at Save** for the operator to + eyeball the mapping. Once they confirm it looks right, Save it and loop the rest unattended. +- **Log every submitted Request Id** (and any component that failed) so the run is auditable and + re-runnable — never silently skip a component. +- Browser automation follows the live UI. If a field, dropdown option, or a popup doesn't match + what's expected, **pause and ask** rather than guessing — a wrong Component Type or a + half-filled request is worse than one clarifying question. +- Don't trigger native file-dialog blocking: use the extension's file-upload path for + attachments, not an OS dialog. diff --git a/references/schlib_parameters.md b/references/schlib_parameters.md index 4dce9b5..b84b496 100644 --- a/references/schlib_parameters.md +++ b/references/schlib_parameters.md @@ -5,50 +5,6 @@ parameters in its component properties (the panel shown in Altium: *Properties This file defines that set, where each value comes from, and how the skill stamps them onto the `.SchLib` symbol. -## The symbol mirrors the workbook (fill everything the datasheet gave) - -**House rule: the symbol's Altium properties must carry _every_ parameter that was filled into -the part's per-typeid workbook from the datasheet — not just the fixed SOP §5 set.** So a CMC -symbol also gets `Rated Current(A)`, `Rated Voltage(V)`, `DC Resistance(mΩ)`, `Package`, -`ESD Withstand Voltage(kV)`, … — whatever that typeid's sheet holds and the datasheet filled. -The SOP set below is the **minimum**; the workbook is the **source of truth** for the rest. - -Build the parameter set straight from the finished `.xlsx` with -`scripts/schlib_params_from_xlsx.py`, which reads the one data row and keeps every **non-empty** -column, then hand its output to `schlib_write.py`: - -```bash -python scripts/schlib_params_from_xlsx.py --xlsx //.xlsx \ - --component \ - --set "Process=Reflow" --set "Datasheet=" \ - [--set "Value="] [--set "Vecmocon Part Code="] \ - --out params.json -python scripts/schlib_write.py --schlib .SchLib --params params.json --out //.SchLib -``` - -What the builder does: - -- **Keeps every filled datasheet column** as a symbol parameter, using the **exact sheet header - as the parameter name** (e.g. `Rated Current(A)`) so the symbol and the workbook stay - traceably identical. Empty columns are left out (the SOP hides blank parameters). -- **Never writes** the identity / versioning / model-link columns — `MPN_make_type`, - `Skill Version`, `Template Version`, `Library Ref/Path`, `Footprint Ref/Path` (Library Ref is - the symbol's own name and the footprint is the linked PCB model, not a text property). -- **Renames** the two columns whose Altium/SOP name differs — `Rohs compliance → ROHS`, - `Operating Temp(°C) → Operating Temperature` — value copied through unchanged. -- **Merges in the SOP-only fields the sheet doesn't hold**: `Manufacturer Part` (= the MPN, - recovered from the tag) is added automatically; pass `Process`, `Datasheet`, and (if known) - `Value` / `Vecmocon Part Code` via `--set` or a `--sop` JSON. A non-empty override wins over a - sheet value; an empty one is ignored. - -Glyph note: Altium stores parameter text as single-byte ANSI, so `schlib_write.py` transliterates -the few unit glyphs that aren't representable — the ohm sign `Ω → Ohm` (so `DC Resistance(mΩ)` -lands as `DC Resistance(mOhm)`) and a Greek micro `μ → u`; `±`, `°` and the latin-1 micro sign -pass through unchanged. So a couple of symbol parameter names are the ASCII form of the sheet -header — expected, not a mismatch. - -The rest of this file describes the SOP §5 minimum set and where each value comes from. - How the parameters get in: the skill writes them **directly into the `.SchLib` in pure Python** via `scripts/schlib_write.py` — it rebuilds the OLE compound file around the enlarged component `Data` stream while preserving every other byte (all other streams, the directory tree, the @@ -137,24 +93,35 @@ symbol's Library Ref (from `altium_refs.py`); omit it to apply to every componen } ``` +## The full parameter set (template + SOP) + +Every `.SchLib` should carry the **complete** parameter set for its part: the **typeid +template's engineering columns** (all columns of that typeid's `template.xlsx` sheet except the +internal bookkeeping ones — the tag `MPN_make_type`, `Skill Version`, `Template Version`, and the +four `Library/Footprint Ref/Path` columns) **plus** the mandatory SOP params above. So a CER +(ceramic MLCC) symbol gets `Capacitance(uF)`, `Tolerance`, `Voltage(V)`, +`Dielectric(temp. Coefficient)`, `Operating Temp(°C)`, `Max operating temp(°C)`, `Package`, +`Description`, `Manufacturer` from the template, alongside `Value`, `Manufacturer Part`, +`Process`, `Vecmocon Part Code`, `ROHS`, `Datasheet`, and the second-source fields. Fill each +from the datasheet; leave blank what the datasheet doesn't state. + ## Writing them into the symbol -Write the parameters straight into the `.SchLib`, producing a new file: +Write the parameters straight into the `.SchLib`, producing a new file. Pass `--typeid` so the +writer guarantees the whole template column set is present (blank where you didn't supply a +value) — this is what keeps every symbol's parameter set complete and consistent: ```bash -python scripts/schlib_write.py --schlib .SchLib --params params.json --out .SchLib +python scripts/schlib_write.py --schlib .SchLib --params params.json --out .SchLib --typeid ``` -`params.json` may carry a `"remove"` list (defaults to `["Manufacturer_Name", -"Manufacturer_Part_Number"]`); those Ultra-Librarian defaults are stripped and the SOP params -added. The script targets the component named in `"component"` (its Library Ref / storage name), -or every component if omitted, and self-checks the output re-opens as a valid OLE. Deliver the -resulting `.SchLib`, and have the engineer open it in Altium once to confirm it loads, then Save -to Server with a revision note per the SOP. - -Scope note: the direct writer keeps a component's `Data` under Altium's 4096-byte mini-stream -threshold in the common case; a very large parameter set (or an extremely long datasheet URL) -can push it past that, at which point fall back to the `altium_params.py` DXP-script path. +`params.json` carries your filled values (and may include a `"remove"` list — defaults to +`["Manufacturer_Name", "Manufacturer_Part_Number"]`, the Ultra-Librarian duplicates that get +stripped). The script targets the component named in `"component"` (its Library Ref / storage +name), or every component if omitted, and self-checks the output re-opens as a valid OLE. It +handles any parameter-set size — small sets stay in Altium's mini-stream, larger ones are written +as a regular stream automatically. Deliver the resulting `.SchLib`; have the engineer open it in +Altium once to confirm it loads, then Save to Server with a revision note per the SOP. Fallback (apply from inside Altium): diff --git a/scripts/schlib_write.py b/scripts/schlib_write.py index e6411d7..4730cd6 100644 --- a/scripts/schlib_write.py +++ b/scripts/schlib_write.py @@ -29,7 +29,7 @@ IMPORTANT: this writes Altium's own binary format from outside Altium. It is val re-open as a well-formed OLE with every other stream byte-identical, but ALWAYS open the result in Altium once to confirm it loads before relying on it. """ -import argparse, json, struct, sys, hashlib, unicodedata +import argparse, json, os, struct, sys, hashlib import olefile FREESECT=0xFFFFFFFF; ENDOFCHAIN=0xFFFFFFFE; FATSECT=0xFFFFFFFD @@ -42,6 +42,26 @@ def le32(b,o): return struct.unpack('