SKILL: stamp symbol parameters automatically in the new-part flow, never as an offer (by admin)

main
admin 2026-07-17 12:49:29 +05:30
parent 474d5830ac
commit 3e5f756916
15 changed files with 160 additions and 346 deletions

279
SKILL.md Normal file → Executable file
View File

@ -2,16 +2,15 @@
name: library-manager name: library-manager
description: >- description: >-
Manage Vecmocon's component library. Extract parameters from a component datasheet PDF into the Manage Vecmocon's component library. Extract parameters from a component datasheet PDF into the
per-typeid Excel template, check Gitea for a duplicate MPN_make, classify to a per-typeid Excel template. Checks Gitea for a duplicate MPN_make, classifies to a typeid, updates that typeid's template (versioning, changelog, backfill), fills
typeid, update that typeid's template (versioning, changelog, backfill), fill a per-part a per-part workbook with human verification, reads the Altium symbol/footprint refs, assembles
workbook, verify with a human, read the Altium symbol/footprint refs, and push a part folder to a part folder and pushes to Gitea. Can UPDATE an existing part instead of hard-stopping, and can
Gitea. Can UPDATE an existing part instead of hard-stopping on a duplicate. WRITES the mandatory WRITE the mandatory SOP parameters directly into a .SchLib symbol while stripping Ultra-Librarian
SOP parameters (from the verified Excel) directly into the .SchLib symbol and COMPILES an Altium defaults. On the .SchLib task it also fills that component's Excel (Description + all parameters)
integrated library (.IntLib) bundling symbol + footprint, so every part folder holds five files and pushes the updated symbol to Gitea, no verification loop. Descriptions
(workbook, datasheet, .SchLib, .PcbLib, .IntLib). Every Description follows Vecmocon's strict follow Vecmocon's strict Altium Description Format. Use whenever the user uploads a datasheet,
Altium Description Format. Use whenever the user uploads a datasheet, builds/updates a library builds/updates a library entry, adds a template parameter, fills/updates .SchLib parameters, or
entry, adds a parameter, fills .SchLib parameters, builds an integrated library, or pushes to pushes a part to Gitea. Always trigger on "\datasheet", "\library", "\library-manager", "\schlib".
Gitea. ALWAYS trigger on "\datasheet", "\library", "\library-manager", or "\schlib".
--- ---
# Library Manager # Library Manager
@ -42,6 +41,16 @@ own, without asking for confirmation**. Never add a "shall I push?" step — jus
the user where it landed. (You still flag a genuine symbol/footprint mismatch, because that's a the user where it landed. (You still flag a genuine symbol/footprint mismatch, because that's a
correctness issue, not a push confirmation.) correctness issue, not a push confirmation.)
**Symbol parameters are a second exception — stamp them without asking, every time.** This
holds in both directions. As a standalone task (the engineer hands over a `.SchLib`): fill that
component's Excel (Description + all parameters) and push the updated symbol automatically,
without pausing to confirm the values. And **inside the normal new-part flow**: once the symbol
is uploaded at step 6, stamp the SOP + sheet parameters onto it and strip the Ultra-Librarian
defaults as a matter of course — do **not** offer it as a follow-up, do **not** ask whether the
engineer wants it, and never push a part folder whose symbol still carries UL defaults. See
*Mandatory symbol parameters* for why this path skips the loop. Every other flow keeps the
normal interactive asks below.
## Inputs ## Inputs
- **A datasheet PDF whose filename is the MPN** (e.g. `BAT46WJ.pdf`). If it's a series - **A datasheet PDF whose filename is the MPN** (e.g. `BAT46WJ.pdf`). If it's a series
@ -75,16 +84,10 @@ library repo/
<MPN>_<make>_<typeid>/ e.g. BAT46WJ_Nexperia_SCH <MPN>_<make>_<typeid>/ e.g. BAT46WJ_Nexperia_SCH
<MPN>_<make>_<typeid>.xlsx this part's own one-row parameter sheet <MPN>_<make>_<typeid>.xlsx this part's own one-row parameter sheet
<MPN>_data.pdf the datasheet <MPN>_data.pdf the datasheet
<symbol>.SchLib user-provided, with all parameters written in <symbol>.SchLib user-provided
<footprint>.PcbLib user-provided <footprint>.PcbLib user-provided
<MPN>.IntLib integrated library (symbol + footprint, compiled by the skill)
``` ```
**Every part folder holds five files** — the workbook, the datasheet, the `.SchLib` (with the
full parameter set written into it), the `.PcbLib`, and the compiled `.IntLib`. The `.SchLib`
parameter fill and the `.IntLib` build are **not optional** — they run on every part before the
push (see *Mandatory symbol parameters* and *Build the integrated library* below).
There is **no single master workbook** — each part carries its own sheet inside its folder. There is **no single master workbook** — each part carries its own sheet inside its folder.
Connection + repo names live in `config/gitea.env` (`SKILL_REPO`, `LIBRARY_REPO`), so runs Connection + repo names live in `config/gitea.env` (`SKILL_REPO`, `LIBRARY_REPO`), so runs
need no per-session token. If the host is unreachable, the git steps fail clearly and write need no per-session token. If the host is unreachable, the git steps fail clearly and write
@ -257,7 +260,9 @@ The part workbook has up to **two sheets**:
Deliver the filled workbook to the user and ask them to verify it. If they report an error Deliver the filled workbook to the user and ask them to verify it. If they report an error
or say it isn't right, **go back to step 4, re-read the datasheet more carefully, re-fill, or say it isn't right, **go back to step 4, re-read the datasheet more carefully, re-fill,
and hand it back.** Repeat until the user confirms it's verified. Nothing is pushed until and hand it back.** Repeat until the user confirms it's verified. Nothing is pushed until
this passes — the engineer is the ground truth for the numbers. this passes — the engineer is the ground truth for the numbers. (The standalone `.SchLib` task
is the exception: it fills the Excel and pushes without this loop — see *Fill the component's
Excel and push* under *Mandatory symbol parameters*.)
### 6. Symbol + footprint → the design columns ### 6. Symbol + footprint → the design columns
@ -292,23 +297,33 @@ python scripts/fill_templates.py part.json \
--template assets/template/template.xlsx --dest <stage>/<tag>/ --design design.json --template assets/template/template.xlsx --dest <stage>/<tag>/ --design design.json
``` ```
Once you have the symbol and its Library Ref, **always** write the full parameter set into the Once you have the symbol and its Library Ref, **stamp the mandatory symbol parameters onto the
`.SchLib` from the verified per-part Excel — see *Mandatory symbol parameters* below. **This is `.SchLib` — always, automatically, without asking.** This is not an optional extra and it is not
compulsory on every run; do not ask the engineer whether to do it — just do it.** It is how the something to offer the engineer: a symbol that reaches Gitea carrying Ultra-Librarian defaults
verified workbook values (and the Description) land in the Altium symbol's properties, and the instead of the SOP set is an incomplete part. Do it in this same run, before step 7, so the
`.IntLib` is then built from this enriched symbol. folder is pushed complete the first time.
### 7. Assemble the part folder — build the `.IntLib`, then five files Build the parameter set from the datasheet values you already read for the workbook (they are the
same values — Manufacturer, Manufacturer Part, Value, Tolerance, Operating Temperature, RoHS,
Datasheet, Process, Vecmocon Part Code, …), **plus every engineering parameter from this typeid's
sheet** and `Component Type` = the part's Class, then write them in and strip the UL defaults —
see *Mandatory symbol parameters* below for the full set and the exact commands. Skip only the
housekeeping columns (`MPN_make_type`, Skill/Template Version) and the four Library/Footprint
Ref/Path columns — those are Altium's own model links, so duplicating them as parameters would
create two sources of truth.
The staging folder `<tag>/` should hold the per-part `<tag>.xlsx`, the datasheet (name it Don't stop to confirm the values here either; the engineer validates the symbol by opening it in
`<MPN>_data.<ext>`), the enriched symbol (`.SchLib` with parameters written in, step 6), and the Altium, which is the real check. Leave genuinely-unknown fields blank (the SOP hides blank
footprint (`.PcbLib`). Now **compile the integrated library** from the enriched symbol + footprint parameters) and **note the gaps in your summary** rather than blocking on a question — the one
so the folder carries all **five** files — see *Build the integrated library* below: field that is never on a datasheet is `Vecmocon Part Code`, so use it if the engineer supplied it
and otherwise leave it blank and say so.
```bash ### 7. Assemble the part folder
python scripts/build_intlib.py --schlib <stage>/<tag>/<sym>.SchLib \
--pcblib <stage>/<tag>/<fp>.PcbLib --out <stage>/<tag>/<MPN>.IntLib The staging folder `<tag>/` should now hold the four files: the per-part `<tag>.xlsx`, the
``` datasheet (name it `<MPN>_data.<ext>`), the symbol (the **stamped** one from step 6, not the raw
upload), and the footprint. If the engineer's upload carried extra files (a `.step` 3D model, a
`.LibPkg`), ask whether to include them — the standard folder is these four.
### 8. Push to the library repo, under the part's Class ### 8. Push to the library repo, under the part's Class
@ -324,11 +339,10 @@ user where it landed.
## Mandatory symbol parameters (.SchLib) ## Mandatory symbol parameters (.SchLib)
The SOP (§5) requires every schematic symbol to carry a fixed parameter set in its Altium The SOP (§5) requires every schematic symbol to carry a fixed parameter set in its Altium
properties — `Manufacturer`, `Manufacturer Part`, `Value`, `Tolerance`, `Operating properties — `Component Type`, `Manufacturer`, `Manufacturer Part`, `Value`, `Tolerance`,
Temperature`, `ROHS`, `Datasheet`, `Process`, `Vecmocon Part Code`, and the two second-source `Operating Temperature`, `ROHS`, `Datasheet`, `Process`, `Vecmocon Part Code`, and the two
fields — with the **Comment** set to the MPN. The skill stamps these onto the symbol from the second-source fields — with the **Comment** set to the MPN. The skill can stamp these onto the
verified workbook. **This step is compulsory on every part build — never ask whether to fill the symbol from the datasheet.
`.SchLib` parameters; always do it** before assembling the folder and building the `.IntLib`.
This runs **as its own task too**, not only inside new-part creation: whenever the user hands 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 over one or more `.SchLib` files and wants their parameters filled/updated (e.g. "\schlib", "add
@ -337,9 +351,13 @@ build its `params.json`, and run `scripts/schlib_write.py` per file — same ste
The skill writes these **directly into the `.SchLib` in pure Python** with The skill writes these **directly into the `.SchLib` in pure Python** with
`scripts/schlib_write.py` (it rebuilds the OLE around the enlarged `Data` stream, preserving `scripts/schlib_write.py` (it rebuilds the OLE around the enlarged `Data` stream, preserving
every other byte, and strips the Ultra-Librarian `Manufacturer_Name` / `Manufacturer_Part_Number` every other byte, and strips the Ultra-Librarian defaults that shouldn't ship: `Manufacturer_Name`
defaults that duplicate the SOP fields). Three kinds of value: / `Manufacturer_Part_Number` (they duplicate the SOP fields), the UL `Copyright` notice, and the
UL `Component_Type` — replaced by Vecmocon's own spaced `Component Type`). Kinds of value:
- **Derived**`Component Type` = the part's **Class** (`Resistor`, `Capacitor`, `Diode`,
`Transistor`, `IC`, …), from `scripts/common.py:class_folder(typeid)` for this part's typeid.
This is the same Class that names its library-repo folder, so the symbol carries it too.
- **Read from the datasheet** (don't just echo given text — open the PDF and fill the real, - **Read from the datasheet** (don't just echo given text — open the PDF and fill the real,
verified values): `Value` = the **value only** in shorthand (e.g. `1u`, `12p`, `10k` — no verified values): `Value` = the **value only** in shorthand (e.g. `1u`, `12p`, `10k` — no
package), plus `Manufacturer`, `Manufacturer Part`, `Operating Temperature`, `Tolerance`, package), plus `Manufacturer`, `Manufacturer Part`, `Operating Temperature`, `Tolerance`,
@ -348,40 +366,21 @@ defaults that duplicate the SOP fields). Three kinds of value:
populate these by default; they stay hidden in Altium until filled later. (An optional populate these by default; they stay hidden in Altium until filled later. (An optional
cross-reference search to find a second source is documented in cross-reference search to find a second source is documented in
`references/schlib_parameters.md` but is currently off — only do it if the engineer asks.) `references/schlib_parameters.md` but is currently off — only do it if the engineer asks.)
- **Ask the engineer** — only `Vecmocon Part Code` (internal, not derivable). - **Fill without pausing** — the `.SchLib` task is non-interactive, so don't stop to ask. The
only genuinely non-derivable field is `Vecmocon Part Code` (internal): use it if the engineer
already supplied it in the request, otherwise leave it blank (the SOP hides blank parameters)
and note the gap in your summary rather than blocking on a question.
Leave any genuinely-unknown field blank — the SOP hides blank parameters, so a gap simply stays Leave any genuinely-unknown field blank — the SOP hides blank parameters, so a gap simply stays
empty until filled. The full method for the second-source search is in empty until filled. The full method for the second-source search is in
`references/schlib_parameters.md`. `references/schlib_parameters.md`.
Write the **full parameter set** — the typeid template's engineering columns **plus** the SOP Collect the values into a `params.json` and write them into the symbol:
params above (see `references/schlib_parameters.md`) — and **source it from the verified per-part
Excel** so the symbol and the workbook can never disagree. After the sheet is verified (step 5),
pass that `<tag>.xlsx` to the writer with `--from-xlsx`: every engineering column **and the
Description** are read straight out of it and written into the `.SchLib`. Then layer on the
**SOP-only** fields that aren't template columns (the `Value` shorthand, `Manufacturer Part`,
`Operating Temperature`, `ROHS`, `Datasheet`, `Process`, `Vecmocon Part Code`, and the blank
second-source pair) via a small `params.json`; on any name collision the `params.json` value
wins. Pass `--typeid` too, so any template column the datasheet left silent is still present
(blank):
```bash ```bash
python scripts/schlib_write.py --schlib <in>.SchLib \ python scripts/schlib_write.py --schlib <in>.SchLib --params params.json --out <stage>/<tag>/<sym>.SchLib
--from-xlsx <stage>/<tag>/<tag>.xlsx --params params.json \
--out <stage>/<tag>/<sym>.SchLib --typeid <TYPEID>
``` ```
(`--params` is optional if the Excel already carries everything you need; `--from-xlsx` is
optional if you'd rather hand-build the whole set in `params.json` — give at least one.) The
`Description` written onto the symbol (and into the component's ComponentDescription field) is
the exact one from the Excel, which was built to `references/description_format.md`.
Passing `--typeid` also fills the symbol's **`Type`** parameter with the component type for that
typeid — the taxonomy **Class** (e.g. `Resistor`, `Capacitor`, `Diode`, `Transistor`,
`Relay / Contactor`, `Inductor / Magnetics`, `Integrated Circuit (IC)`) — so the symbol
self-describes what kind of part it is. It's derived from the typeid (which came from the
datasheet), so it's set automatically; only an explicit `Type` in the Excel/params overrides it.
Deliver the resulting `.SchLib`; the engineer opens it in Altium once to confirm it loads, then 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 **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 `params.json` shape (incl. the `remove` list), and the mini-stream size caveat are in
@ -390,39 +389,54 @@ 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 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. skill writes Altium's own binary format, so Altium is the final validator.
## Build the integrated library (.IntLib) ### Fill the component's Excel and push — automatically, no verification loop
Every part also gets a compiled **integrated library** — one file that bundles the schematic The datasheet values you just read for the symbol belong in that component's per-part Excel
symbol (with the parameters written in) and its footprint, so the component resolves in Altium workbook too, so fill them in the **same pass and push the result without asking**. This is a
with no separate `.PcbLib` to locate. `scripts/build_intlib.py` compiles it in pure Python, no deliberate exception to the skill's usual "verify before Gitea" rule: when the engineer hands
Altium needed, and it's the **fifth file** in every part folder. over a symbol for the `.SchLib` task, they want the library entry brought fully up to date in
one shot — the Description and every parameter written into the Excel, the updated symbol saved
beside it, and the whole thing pushed — not a round of confirmation questions. The engineer is
already reviewing the symbol in Altium, so a separate spreadsheet verification loop adds delay
without adding safety. The one check that still stands is the symbol/footprint-vs-part match: if
the symbol clearly isn't this component, stop and flag it rather than pushing the wrong part.
```bash 1. **Fill the Excel automatically.** Reuse the values you already read for the symbol to build
python scripts/build_intlib.py --schlib <stage>/<tag>/<sym>.SchLib \ `part.json` — with `Description` built to `references/description_format.md` and every
--pcblib <stage>/<tag>/<fp>.PcbLib --out <stage>/<tag>/<MPN>.IntLib parameter the datasheet states — and fill the per-part sheet **directly, skipping the human
``` verification loop (step 5)**. Do this inside a checkout of the library repo so it can be
pushed; for a part already in Gitea, locate it with `find-part` exactly as in *Updating an
existing part*, and re-derive the design columns from the symbol/footprint already in the
folder so they carry through unchanged:
How it works and what it needs: ```bash
python scripts/gitea_components.py checkout --dest work/
python scripts/gitea_components.py find-part --mpn <MPN> --make <make> --root work/ --json
python scripts/altium_refs.py design \
--symbol work/<Class>/<tag>/<sym>.SchLib \
--footprint work/<Class>/<tag>/<footprint>.PcbLib > design.json
python scripts/fill_templates.py part.json \
--template assets/template/template.xlsx --dest work/<Class>/<tag>/ --design design.json
```
- Feed it the **enriched `.SchLib`** (after `schlib_write.py` has written the parameters in) so the (If `find-part` returns `NOT FOUND`, the part isn't in Gitea yet — assemble the folder as a
integrated symbol carries the full parameter set and the correct ComponentDescription. new part per steps 47, still without the verification loop, then push with `push-part`.)
- The symbol **must contain a footprint model link** (an Altium RECORD=45 `ModelName` /
`ModelType=PCBLIB` in its `Data` stream — Ultra-Librarian and Altium exports include this). The 2. **Drop in the updated symbol and push — no confirmation.** Copy the `.SchLib` you wrote with
builder reads that link to know which footprint to bind, and errors clearly if it's absent — in `schlib_write.py` into the same part folder under its proper name, then push the updated part
that case the symbol has no footprint assigned, so fix the symbol (or re-export it) first. — the symbol and the freshly filled Excel together — automatically:
- It builds the `.IntLib` as an OLE compound file with five streams — the embedded `.schlib` and
`.pcblib` (zlib-compressed at Altium's default level), plus `LibCrossRef.Txt`, `Parameters .bin`, ```bash
and `Version.Txt` — reusing a bundled container skeleton cp <stage>/<tag>/<sym>.SchLib work/<Class>/<tag>/<sym>.SchLib
(`assets/templates/intlib_container.IntLib`) for the exact directory layout Altium expects, and a python scripts/gitea_components.py commit-push --root work/ \
**FAT-first** compound-file writer (`build_intlib.write_cfb`). Both the outer container and the --message "update <tag>: fill parameters + symbol (by <operator name>)" \
embedded symbol are written FAT-first — this matters: a FAT-last layout re-opens fine in olefile --author "<operator name> <<operator email>>"
and even standalone in Altium, but Altium's **IntLib extractor** throws "Stream read error" on it. ```
The two embedded libraries are compressed at zlib's **default level** (`0x789c`); Altium's
decompressor rejects other levels (e.g. level-9 `0x78da`). Pushing without asking is consistent with the skill's standing rule that pushing is automatic;
- The builder self-validates: it re-opens the output, decompresses both embedded libraries, and what's new here is that the Excel fill is automatic too. Tell the user which parameters you
confirms they round-trip and that the cross-reference names the symbol + footprint. Even so, filled, that the symbol was updated, and where in Gitea it landed. (A part-data fill isn't a
Altium is the final validator — have the engineer open the `.IntLib` once (or, as a guaranteed template change, so nothing version-bumps and the changelog is untouched.)
fallback, compile a `.LibPkg` in Altium from the same `.SchLib` + `.PcbLib`).
## Per-typeid versioning ## Per-typeid versioning
@ -533,81 +547,4 @@ change: nothing is overwritten until the engineer has verified the new version.
- **Symbol/footprint** → copy the new `.SchLib`/`.PcbLib` into the folder under their proper - **Symbol/footprint** → copy the new `.SchLib`/`.PcbLib` into the folder under their proper
names (replacing the old ones), then re-derive against the **new** files and rebuild with names (replacing the old ones), then re-derive against the **new** files and rebuild with
`--design` exactly as above (this is step 6 of the add flow). Still flag a genuine `--design` exactly as above (this is step 6 of the add flow). Still flag a genuine
symbol/footprint-vs-part mismatch — that's a correctness issue. symbol/footprint-vs-part misma
- **Datasheet** → drop the newer PDF in as `<MPN>_data.<ext>`, replacing the old one. If the
values should reflect it, also redo the values step above.
4. **Verify, then push.** Hand the rebuilt `<tag>.xlsx` back and run the same human
verification loop (step 5) — the engineer is still the ground truth. Once confirmed, commit
the checkout and push (attributed to the operator, with a message that says it's an update):
```bash
python scripts/gitea_components.py commit-push --root work/ \
--message "update <tag>: <what changed> (by <operator name>)" \
--author "<operator name> <<operator email>>"
```
The authored commit and message are the record of the revision (visible in `git log`, the
Gitea commit view and `git blame`); the changelog stays reserved for template/version
changes, not per-part data fixes. Tell the user what changed and where it landed.
## Pushing the skill repo
When skill files change (a new typeid template, a parameter add, a version/changelog bump),
push the skill's own files to the skill repo with `push-skill` **automatically** (no
confirmation):
```bash
python scripts/gitea_components.py push-skill --author "<operator name> <<operator email>>" \
--message "Sync skill files + changelog"
```
`push-skill` clones the skill repo, copies the skill files in with the **`GIT_TOKEN` blanked
out** (the real token never leaves the machine), and **merges** `CHANGELOG.xlsx` — appending
this run's new rows onto the changelog already in Gitea so earlier entries are preserved — then
writes the merged changelog back locally. (The older `push_to_gitea.sh` still exists for a
plain flat push, but it does not merge the changelog or blank the token, so prefer
`push-skill` for the skill repo.)
## Resources
- `assets/template/template.xlsx` — the master template: one sheet per **typeid** (125),
source of every sheet's headers, styling and order. Columns A/B/C are always
`MPN_make_type` / `Skill Version` / `Template Version`; `Library Ref/Path`,
`Footprint Ref/Path` and `Manufacturer` sit near the end.
- `assets/template/Type_ID.xlsx` + `references/taxonomy.md` — Class → Subclass → Type ID.
- `references/description_format.md` — Vecmocon's Altium **Description Format** (the `_`-joined
engineering string for each part's Description column). Defines a format for **every** type:
the four SOP-defined ones (RES/CAP/Zener/TVS) are strict, the rest are the house extension on
the same basis. Read it before filling any Description.
- `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.
- `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`).
- `scripts/common.py` — taxonomy loader (`load_taxonomy`, `class_folder`), version store
(`get_versions`, `version_labels`, `bump_versions`), and the tag helper (`part_tag`).
- `scripts/fill_templates.py` — build one per-part `<tag>.xlsx` (version-stamped); reused for
backfill.
- `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_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/build_intlib.py` — compile a component's `.SchLib` + `.PcbLib` into an Altium
**integrated library** (`.IntLib`) in pure Python (FAT-first OLE writer + Altium-level zlib);
the fifth file in every part folder. Needs the enriched `.SchLib` (parameters written) with a
footprint model link. Uses `assets/templates/intlib_container.IntLib` as the container skeleton.
- `assets/templates/intlib_container.IntLib` — a known-good single-component `.IntLib` reused
purely as the OLE container skeleton by `build_intlib.py` (all its streams are overwritten).
- `scripts/gitea_components.py``check-mpn`, `find-part` (locate an existing part to
update), `checkout`, `list-type`, `place-part`, `commit-push`, `push-part` (library repo),
and `push-skill` (skill repo: token-blanked push + append-only changelog merge).
- `scripts/push_to_gitea.sh` — push a folder's contents to a Gitea repo (used for the skill
repo).
- `config/gitea.env` — host, user, token, and the `SKILL_REPO` / `LIBRARY_REPO` names
(**secret** — do not push the token).

Binary file not shown.

0
assets/template/Type_ID.xlsx Normal file → Executable file
View File

0
assets/vecmocon-logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

0
references/description_format.md Normal file → Executable file
View File

68
references/schlib_parameters.md Normal file → Executable file
View File

@ -9,7 +9,9 @@ How the parameters get in: the skill writes them **directly into the `.SchLib` i
via `scripts/schlib_write.py` — it rebuilds the OLE compound file around the enlarged component 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 `Data` stream while preserving every other byte (all other streams, the directory tree, the
Altium CLSIDs). It also **removes the Ultra-Librarian default params `Manufacturer_Name` and Altium CLSIDs). It also **removes the Ultra-Librarian default params `Manufacturer_Name` and
`Manufacturer_Part_Number`**, which just duplicate the SOP `Manufacturer` / `Manufacturer Part`. `Manufacturer_Part_Number`** (which duplicate the SOP `Manufacturer` / `Manufacturer Part`), the
UL **`Copyright`** notice (Vecmocon symbols don't carry it), and the UL **`Component_Type`**
(underscore) — replaced by Vecmocon's own spaced **`Component Type`** parameter (see below).
The output is a ready `.SchLib`. Because this writes Altium's own format from outside Altium, the The output is a ready `.SchLib`. Because this writes Altium's own format from outside Altium, the
script self-checks that the result re-opens as a valid OLE with the params present — but **always script self-checks that the result re-opens as a valid OLE with the params present — but **always
open the result in Altium once to confirm it loads** before relying on it. (An older path, open the result in Altium once to confirm it loads** before relying on it. (An older path,
@ -20,10 +22,24 @@ inside Altium; keep it as a fallback if a particular file doesn't round-trip.)
Use these exact Altium parameter names (they must match the symbol, per the SOP screenshot). Use these exact Altium parameter names (they must match the symbol, per the SOP screenshot).
The **Comment** field is set to the MPN (SOP §4), and the **Description** field is the strict The **Comment** field is set to the MPN (SOP §4), and the **Description** field is the strict
string from `references/description_format.md`. string from `references/description_format.md` — which **overwrites** whatever prose Ultra
Librarian put in `Description` (UL's text is often wrong: a real 6N137 export read
`SMD-8 CPLR SNGL 10MBD 100V/us -e3` when the datasheet CMR is 1000 V/µs).
Beyond this SOP set, also stamp on **every engineering parameter from the part's typeid sheet**
(for an `ISO`: Isolator Type, Isolation Voltage, No. of Channels, Data Rate, Supply Voltage,
Creepage, Package, Power, Max Output Current). Skip the housekeeping columns (`MPN_make_type`,
Skill/Template Version) and the four Library/Footprint Ref/Path columns — Altium already holds
those as the symbol's model links, so repeating them as parameters makes two sources of truth.
**Encoding gotcha:** Altium parameter records are **latin-1**, so a value containing `≥`, `≤`,
`±`-beyond-latin1, `µ` (U+00B5 is fine, U+03BC is not) or similar will crash `schlib_write.py`
with a `UnicodeEncodeError`. Rephrase into latin-1 (`≥ 7 mm` → `7 mm min.`) rather than dropping
the value; `°` (U+00B0) is latin-1 and safe. The workbook keeps the original notation.
| Parameter | Source | Notes | | Parameter | Source | Notes |
|-----------|--------|-------| |-----------|--------|-------|
| `Component Type` | derived | the part's **Class**`Resistor`, `Capacitor`, `Diode`, `Transistor`, `IC`, … — from the taxonomy row for its typeid (`scripts/common.py:class_folder(typeid)`) |
| `Value` | datasheet | the component **value only** (no package), in shorthand — e.g. `1u`, `12p`, `100n`, `10k` | | `Value` | datasheet | the component **value only** (no package), in shorthand — e.g. `1u`, `12p`, `100n`, `10k` |
| `Manufacturer Part` | datasheet | the MPN; also the **Comment** field | | `Manufacturer Part` | datasheet | the MPN; also the **Comment** field |
| `Manufacturer` | datasheet | manufacturer name as printed, e.g. `YAGEO` | | `Manufacturer` | datasheet | manufacturer name as printed, e.g. `YAGEO` |
@ -41,7 +57,9 @@ handed to you.** Open the PDF, find each real value (`Value`, `Manufacturer Part
`Manufacturer`, `Operating Temperature`, `Tolerance`, `Datasheet`, `ROHS`, and `Process` by `Manufacturer`, `Operating Temperature`, `Tolerance`, `Datasheet`, `ROHS`, and `Process` by
inference from the package), and fill them verified. An honest blank beats a guess — the SOP inference from the package), and fill them verified. An honest blank beats a guess — the SOP
hides blank parameters, so a gap just stays empty until someone fills it. Only **one** field is hides blank parameters, so a gap just stays empty until someone fills it. Only **one** field is
purely internal and must come from the engineer: `Vecmocon Part Code` — ask for it. purely internal and is never on a datasheet: `Vecmocon Part Code`. Use it if the engineer already
supplied it; otherwise **leave it blank and note the gap in your summary — don't stop to ask**.
Stamping the symbol is automatic and non-interactive (SKILL.md, step 6), so no field blocks it.
The second-source pair (`Manufacturer 2` / `Manufacturer Part 2`) is **left blank for now** The second-source pair (`Manufacturer 2` / `Manufacturer Part 2`) is **left blank for now**
don't populate it by default. It simply stays hidden in Altium until someone fills it later. don't populate it by default. It simply stays hidden in Altium until someone fills it later.
@ -78,6 +96,7 @@ symbol's Library Ref (from `altium_refs.py`); omit it to apply to every componen
"component": "CC0402FRNPO9BN120", "component": "CC0402FRNPO9BN120",
"comment": "CC0402FRNPO9BN120", "comment": "CC0402FRNPO9BN120",
"parameters": { "parameters": {
"Component Type": "Capacitor",
"Value": "12pF_0402", "Value": "12pF_0402",
"Manufacturer Part": "CC0402FRNPO9BN120", "Manufacturer Part": "CC0402FRNPO9BN120",
"Manufacturer": "YAGEO", "Manufacturer": "YAGEO",
@ -93,41 +112,22 @@ 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 ## Writing them into the symbol
Write the parameters straight into the `.SchLib`, producing a new file. Pass `--typeid` so the Write the parameters straight into the `.SchLib`, producing a new file:
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 ```bash
python scripts/schlib_write.py --schlib <in>.SchLib --params params.json --out <out>.SchLib --typeid <TYPEID> python scripts/schlib_write.py --schlib <in>.SchLib --params params.json --out <out>.SchLib
``` ```
`params.json` carries your filled values (and may include a `"remove"` list — defaults to `params.json` may carry a `"remove"` list (defaults to `["Manufacturer_Name",
`["Manufacturer_Name", "Manufacturer_Part_Number"]`, the Ultra-Librarian duplicates that get "Manufacturer_Part_Number", "Copyright", "Component_Type"]`); those Ultra-Librarian defaults are
stripped). The script targets the component named in `"component"` (its Library Ref / storage stripped and the SOP params — including the spaced `Component Type` = Class — added. Real UL
name), or every component if omitted, and self-checks the output re-opens as a valid OLE. It exports also ship **placeholder** params whose Text is just the name back again (`Type` = `Type`,
handles any parameter-set size — small sets stay in Altium's mini-stream, larger ones are written `RefDes` = `RefDes`, sometimes `TYPE`); add those to `remove` too — they're noise, not data. After
as a regular stream automatically. Deliver the resulting `.SchLib`; have the engineer open it in writing, grep the output for `Ultra Librarian` / `Manufacturer_Name` and confirm the count is 0. The script targets the component named in `"component"` (its Library Ref / storage name),
Altium once to confirm it loads, then Save to Server with a revision note per the SOP. 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.
Fallback (apply from inside Altium): Scope note: the d
```bash
python scripts/altium_params.py script --params params.json --out apply_params.pas
```
Then in Altium: open the `.SchLib`, **DXP → Run Script… → ApplyParameters**, review, **Save to
Server**.

0
references/taxonomy.md Normal file → Executable file
View File

0
scripts/altium_params.py Normal file → Executable file
View File

0
scripts/altium_refs.py Normal file → Executable file
View File

0
scripts/append_parameter.py Normal file → Executable file
View File

0
scripts/common.py Normal file → Executable file
View File

0
scripts/fill_templates.py Normal file → Executable file
View File

0
scripts/gitea_components.py Normal file → Executable file
View File

0
scripts/push_to_gitea.sh Normal file → Executable file
View File

159
scripts/schlib_write.py Normal file → Executable file
View File

@ -12,39 +12,33 @@ move are recomputed.
What it does to the target component's Data stream: What it does to the target component's Data stream:
- removes the Ultra-Librarian default params ``Manufacturer_Name`` / ``Manufacturer_Part_Number`` - removes the Ultra-Librarian default params ``Manufacturer_Name`` / ``Manufacturer_Part_Number``
(they duplicate the SOP ``Manufacturer`` / ``Manufacturer Part`` override with --keep or a (they duplicate the SOP ``Manufacturer`` / ``Manufacturer Part``), the UL ``Copyright`` notice,
"remove" list in params.json), and the UL ``Component_Type`` (Vecmocon adds its own spaced ``Component Type`` = Class instead);
override this default set with a "remove" list in params.json,
- adds/updates the SOP parameters from params.json (see references/schlib_parameters.md), - adds/updates the SOP parameters from params.json (see references/schlib_parameters.md),
- leaves pins, graphics, the Comment and all other records exactly as they were. - leaves pins, graphics, the Comment and all other records exactly as they were.
Usage: Usage:
# source parameters + Description from the verified per-part workbook (preferred):
python schlib_write.py --schlib IN.SchLib --from-xlsx <tag>.xlsx \
[--params sop.json] --typeid ELE --out OUT.SchLib
# or from a params.json alone:
python schlib_write.py --schlib IN.SchLib --params params.json --out OUT.SchLib python schlib_write.py --schlib IN.SchLib --params params.json --out OUT.SchLib
--from-xlsx reads the filled per-part `<tag>.xlsx` (the same workbook the engineer verified) and
writes every engineering column + the Description into the symbol, so the .SchLib and the sheet
never diverge. --params then layers on the SOP-only fields that aren't template columns
(Value shorthand, Process, ROHS, Datasheet, Manufacturer Part, Vecmocon Part Code, ...); on a
name collision the params.json value wins. Give either or both (at least one).
params.json (same shape altium_params.py uses): params.json (same shape altium_params.py uses):
{"component":"JMK105BJ105KV-F", # LibRef / component-storage name; omit -> all comps {"component":"JMK105BJ105KV-F", # LibRef / component-storage name; omit -> all comps
"parameters":{"Value":"1u","Manufacturer":"Taiyo Yuden", ...}, "parameters":{"Value":"1u","Manufacturer":"Taiyo Yuden","Component Type":"Capacitor", ...},
"remove":["Manufacturer_Name","Manufacturer_Part_Number"]} # optional; this is the default "remove":["Manufacturer_Name","Manufacturer_Part_Number","Copyright","Component_Type"]} # optional; default
IMPORTANT: this writes Altium's own binary format from outside Altium. It is validated to IMPORTANT: this writes Altium's own binary format from outside Altium. It is validated to
re-open as a well-formed OLE with every other stream byte-identical, but ALWAYS open the result 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. in Altium once to confirm it loads before relying on it.
""" """
import argparse, json, os, struct, sys, hashlib import argparse, json, struct, sys, hashlib
import olefile import olefile
FREESECT=0xFFFFFFFF; ENDOFCHAIN=0xFFFFFFFE; FATSECT=0xFFFFFFFD FREESECT=0xFFFFFFFF; ENDOFCHAIN=0xFFFFFFFE; FATSECT=0xFFFFFFFD
SEC=512; MINI=64; CUTOFF=4096 SEC=512; MINI=64; CUTOFF=4096
DEFAULT_REMOVE=["Manufacturer_Name", "Manufacturer_Part_Number"] # Ultra-Librarian defaults we strip: the two that duplicate the SOP Manufacturer / Manufacturer
# Part, the UL "Copyright" notice (Vecmocon symbols don't carry it), and the UL "Component_Type"
# (underscore) — Vecmocon adds its own spaced "Component Type" = the part's Class instead.
DEFAULT_REMOVE=["Manufacturer_Name", "Manufacturer_Part_Number", "Copyright", "Component_Type"]
def le16(b,o): return struct.unpack('<H',b[o:o+2])[0] def le16(b,o): return struct.unpack('<H',b[o:o+2])[0]
@ -52,62 +46,6 @@ def le32(b,o): return struct.unpack('<I',b[o:o+4])[0]
def pad(b,n): return b+b'\x00'*((-len(b))%n) def pad(b,n): return b+b'\x00'*((-len(b))%n)
# ----------------------------------------------------------------- typeid template columns
# Template columns that are internal library bookkeeping, NOT symbol parameters.
NON_PARAM_COLS = {"MPN_make_type", "Skill Version", "Template Version",
"Library Ref", "Library Path", "Footprint Ref", "Footprint Path"}
def template_param_names(template_path, typeid):
"""The symbol-parameter columns for a typeid = every column on that typeid's template sheet
EXCEPT the internal bookkeeping ones (the tag, the two version columns, and the four design
Ref/Path columns). These are the engineering parameters that belong on the symbol."""
import openpyxl
wb = openpyxl.load_workbook(template_path, read_only=True)
if typeid not in wb.sheetnames:
raise SystemExit(f"no template sheet for typeid '{typeid}'")
ws = wb[typeid]
return [ws.cell(1, c).value for c in range(1, ws.max_column + 1)
if ws.cell(1, c).value and ws.cell(1, c).value not in NON_PARAM_COLS]
def component_type_for(typeid):
"""The human-readable component TYPE for a typeid (e.g. 'Resistor', 'Capacitor',
'Relay / Contactor', 'Inductor / Magnetics', 'Integrated Circuit (IC)') taken from the library
taxonomy's Class. Written into the symbol's `Type` parameter so the .SchLib self-describes what
kind of part it is. Returns None if the taxonomy can't be loaded or the typeid is unknown."""
if not typeid:
return None
try:
import common
return (common.load_taxonomy().get(typeid.upper()) or {}).get("class")
except Exception:
return None
def params_from_xlsx(xlsx_path, sheet=None):
"""Read the filled parameter values out of a per-part workbook (`<tag>.xlsx`) — row 1 is the
headers, row 2 is the single data row this skill writes. Returns {header: value} for every
engineering column (i.e. NOT the bookkeeping/design columns in NON_PARAM_COLS), INCLUDING the
Description column. This is what lets the .SchLib carry exactly the parameters + Description
the engineer already verified in Excel the two never diverge. Blank cells stay blank.
By default it reads the first sheet (the typeid parameter sheet). A `Version History` second
sheet, if present, is skipped."""
import openpyxl
wb = openpyxl.load_workbook(xlsx_path, read_only=True, data_only=True)
ws = wb[sheet] if sheet else wb[wb.sheetnames[0]]
headers = [ws.cell(1, c).value for c in range(1, ws.max_column + 1)]
values = [ws.cell(2, c).value for c in range(1, ws.max_column + 1)]
out = {}
for h, v in zip(headers, values):
if not h or h in NON_PARAM_COLS:
continue
out[str(h)] = "" if v is None else str(v)
return out
# ----------------------------------------------------------------- read the container # ----------------------------------------------------------------- read the container
def read_container(path): def read_container(path):
@ -159,7 +97,7 @@ def _uid(name):
def _param_record(idx, name, value): def _param_record(idx, name, value):
s=(f"|RECORD=41|IndexInSheet={idx}|OwnerPartId=1|Justification=4|FontID=2|IsHidden=T" s=(f"|RECORD=41|IndexInSheet={idx}|OwnerPartId=1|Justification=4|FontID=2|IsHidden=T"
f"|Text={value}|Name={name}|UniqueID={_uid(name)}") f"|Text={value}|Name={name}|UniqueID={_uid(name)}")
payload=s.encode('utf-8')+b'\x00' # utf-8: handles Ω, µ, °, ± in names/values payload=s.encode('latin-1')+b'\x00'
return struct.pack('<I',len(payload))+payload return struct.pack('<I',len(payload))+payload
@ -180,38 +118,20 @@ def _leading_text_records(data):
def _rec_name(block): def _rec_name(block):
t=block[4:-1].decode('utf-8','replace') t=block[4:-1].decode('latin-1')
return t.split('|Name=')[1].split('|')[0] if '|Name=' in t else None return t.split('|Name=')[1].split('|')[0] if '|Name=' in t else None
def _patch_field(block, field, value):
"""Replace |field=...| inside a length-prefixed text record, re-framing its 4-byte length.
Used to set the component's ComponentDescription in the RECORD=1 header."""
import re
text = block[4:-1].decode('utf-8', 'replace')
if f"|{field}=" in text:
text = re.sub(rf"\|{re.escape(field)}=[^|]*", f"|{field}={value}", text, count=1)
elif text.startswith("|RECORD="):
text = text + f"|{field}={value}"
payload = text.encode('utf-8') + b'\x00'
return struct.pack('<I', len(payload)) + payload
def edit_data(data, params, remove): def edit_data(data, params, remove):
"""Return a new Data stream: drop `remove` params, drop any SOP-name params (re-added """Return a new Data stream: drop `remove` params, drop any SOP-name params (re-added
fresh), keep everything else, then append the SOP params. Also mirror the `Description` fresh), keep everything else, then append the SOP params. Pins/graphics/tail untouched."""
parameter into the component's ComponentDescription field (the Altium 'Description' shown in
the component properties). Pins/graphics/tail untouched."""
leading, tail = _leading_text_records(data) leading, tail = _leading_text_records(data)
sop_names=set(params) sop_names=set(params)
desc = params.get("Description")
kept=[] kept=[]
for blk in leading: for blk in leading:
nm=_rec_name(blk) nm=_rec_name(blk)
if nm is not None and (nm in remove or nm in sop_names): if nm is not None and (nm in remove or nm in sop_names):
continue # drop UL duplicates + stale SOP copies continue # drop UL duplicates + stale SOP copies
if desc is not None and blk[4:-1].startswith(b"|RECORD=1|"):
blk = _patch_field(blk, "ComponentDescription", desc) # component Description field
kept.append(blk) kept.append(blk)
added=[_param_record(20+i, nm, val) for i,(nm,val) in enumerate(params.items())] added=[_param_record(20+i, nm, val) for i,(nm,val) in enumerate(params.items())]
return b''.join(kept)+b''.join(added)+tail return b''.join(kept)+b''.join(added)+tail
@ -295,37 +215,10 @@ def rebuild(entries, content):
# ----------------------------------------------------------------- driver # ----------------------------------------------------------------- driver
def write_params(schlib, params_json, out, typeid=None, template=None, from_xlsx=None): def write_params(schlib, params_json, out):
params_json = params_json or {}
component=params_json.get("component") or None component=params_json.get("component") or None
fields=params_json.get("parameters", {}) or {}
remove=params_json.get("remove", DEFAULT_REMOVE) remove=params_json.get("remove", DEFAULT_REMOVE)
# Base the parameter set on the verified per-part Excel when given (--from-xlsx): every
# engineering column and the Description come straight from the workbook the engineer already
# verified, so the symbol and the sheet can never disagree. Explicit params.json entries
# (the SOP-only fields like Value/Process/ROHS/Datasheet/Vecmocon Part Code that aren't
# template columns) are layered on top and win on any name collision.
fields = {}
if from_xlsx:
fields.update(params_from_xlsx(from_xlsx))
fields.update(params_json.get("parameters", {}) or {})
# If a typeid+template are given, guarantee the FULL template parameter set is written:
# every engineering column for that typeid becomes a symbol parameter (value from the
# params if provided, else blank). This is what makes every .SchLib carry the complete,
# consistent parameter set the template defines — not just whatever was hand-listed.
typeid = typeid or params_json.get("typeid")
template = template or params_json.get("template")
# Set the symbol's `Type` parameter to the component type for this typeid (Resistor, Capacitor,
# Relay / Contactor, ...), from the taxonomy Class — unless one was explicitly provided. This
# makes the symbol self-describe what kind of part it is.
if typeid:
ct = component_type_for(typeid)
if ct:
fields.setdefault("Type", ct)
if typeid and template:
for name in template_param_names(template, typeid):
fields.setdefault(name, "")
entries, paths, content = read_container(schlib) entries, paths, content = read_container(schlib)
# target Data stream sid(s): a stream named 'Data' whose parent storage == component (or all) # target Data stream sid(s): a stream named 'Data' whose parent storage == component (or all)
@ -349,7 +242,7 @@ def write_params(schlib, params_json, out, typeid=None, template=None, from_xlsx
ok=True ok=True
for e in ole.listdir(streams=True): for e in ole.listdir(streams=True):
if e[-1].lower()=="data": if e[-1].lower()=="data":
t=ole.openstream(e).read().decode('utf-8','ignore') t=ole.openstream(e).read().decode('latin-1','ignore')
for nm in fields: for nm in fields:
if f"|Name={nm}|" not in t and f"|Name={nm}\x00" not in t and f"Name={nm}" not in t: if f"|Name={nm}|" not in t and f"|Name={nm}\x00" not in t and f"Name={nm}" not in t:
ok=False ok=False
@ -362,26 +255,10 @@ def write_params(schlib, params_json, out, typeid=None, template=None, from_xlsx
def main(): def main():
ap=argparse.ArgumentParser() ap=argparse.ArgumentParser()
ap.add_argument("--schlib", required=True) ap.add_argument("--schlib", required=True)
ap.add_argument("--params", help="params.json with SOP fields (Value/Process/ROHS/Datasheet/" ap.add_argument("--params", required=True)
"Vecmocon Part Code/...) + optional 'component'/'remove'. "
"Optional if --from-xlsx supplies the parameters.")
ap.add_argument("--from-xlsx", dest="from_xlsx",
help="the verified per-part <tag>.xlsx: every engineering column + the "
"Description are written into the .SchLib straight from it, so the "
"symbol matches the sheet. Layer SOP-only fields on with --params.")
ap.add_argument("--out", required=True) ap.add_argument("--out", required=True)
ap.add_argument("--typeid", help="component typeid; with --template, guarantees that typeid's "
"full template parameter set is present (blank where absent)")
ap.add_argument("--template", help="path to template.xlsx (defaults to the skill's)")
a=ap.parse_args() a=ap.parse_args()
if not a.params and not a.from_xlsx: write_params(a.schlib, json.load(open(a.params, encoding="utf-8")), a.out)
ap.error("give --params and/or --from-xlsx (at least one source of parameters)")
template = a.template
if a.typeid and not template:
template = os.path.join(os.path.dirname(__file__), "..", "assets", "template", "template.xlsx")
params_json = json.load(open(a.params, encoding="utf-8")) if a.params else {}
write_params(a.schlib, params_json, a.out,
typeid=a.typeid, template=template, from_xlsx=a.from_xlsx)
if __name__ == "__main__": if __name__ == "__main__":