Imported from dmitry-sorkin/k3d-site (
AGENTS.md). Install upstream withnpx skills add dmitry-sorkin/k3d-site. Copyright stays with the author.
AGENTS
Russian-language documentation site for the K3D 3D-printing community, built with a custom-named MkDocs Material toolchain.
Build
-
Config:
properdocs.yml(NOTmkdocs.yml). Build tool is theproperdocsCLI, a renamed mkdocs. -
Source:
docs/. Output:site/(gitignored). -
Python deps live in
.github/workflows/requirements.txt(mkdocs 1.6.1, properdocs 1.6.7, mkdocs-materialx, glightbox, redirects, charts, table-reader, document-dates, pillow). -
Local build:
python -m venv .venv && .venv/Scripts/activate pip install -r .github/workflows/requirements.txt properdocs build --cleanDev server:
properdocs serve(port 8000 by default). No tests, no linter — this is a static site. -
Deploy:
.github/workflows/main.yamlbuilds on push/PR tomain,build-stage-python,test-lolo; deploys via rsync-over-SSH only on push tomain. Secrets:SSH_PRIVATE_KEY,SSH_USERNAME,SSH_SERVER,DEPLOY_PATH.
Layout
docs/
index.md # landing page, no nav
authors.yml # author registry (sorkin, makarov, alexander)
part-navi/ # 3D recommender (printers, extruder, electronics, mechanics, filament, dryers, consumables)
vostok/ # K3D VOSTOK printer: index, faq, releases, manual/, gallery/, v8/ (archive)
calibrations/ # PID, IS, flow, accuracy/, VFA, retractions/, la/ (PA), max_flowrate/
projects/ # ps_translation, custom_pulleys, printheads (ebp/ehp/esp), feeders (bemege/minifeeder/feeder965)
printers/ # per-printer mods (kobra3, k1, neptune3pro/4, kp3s, qidi_q1_pro, qidi_q2)
articles/ # long-form posts (print_strong, custom_gcode, nozzle_test_2024, ultra_high_flow_hotends, ad5m_ad5x_hotend_test, cooling_problems)
redirects/ # stub .md files whose real target is in properdocs.yml `redirect_maps`
pics/, assets/, models/ # images and 3D models referenced from MD
stylesheets/ # extra9.css referenced from theme
javascripts/ # mathjax loader
overrides/ # Material theme partial overrides (partials/integrations/, .icons/)
tools/gallery/ # tracked build helpers (gallery/extract.py, gallery/build.py, gallery/README.md)
site/ # build output, gitignored
.github/workflows/ # CI
Nav structure is defined explicitly in properdocs.yml under nav: — folder names mirror nav sections but you must add new pages to both.
Conventions
-
All content is Russian. Keep filenames ASCII; titles and headings in Russian.
-
Every page starts with YAML frontmatter. Minimum expected keys (see
docs/index.md):--- authors: - sorkin title: ... description: ... ---Optional:
hide: [navigation, toc],show_created,show_updated,show_author. Authors must exist indocs/authors.yml. -
Use Material admonitions and these custom ones defined in config:
fire,speed,time,clone(icons: fire, speedometer, clock-outline, mirror). -
Mermaid, Vega-Lite, MathJax, tabs, keys, superfences, critic/caret/mark/tilde are enabled — use them instead of screenshots or hand-drawn diagrams.
-
Inline images: place in the same section's folder or in
docs/pics/, reference with relative paths.
Mermaid diagrams
-
Always set
labelColorandlineColorto the theme CSS variables in the diagram'sthemeVariablesfrontmatter — otherwise colors don't follow light/dark mode and are unreadable on dark backgrounds. The safe template for any mermaid block:```mermaid --- config: themeVariables: labelColor: 'var(--md-mermaid-label-fg-color)' lineColor: 'var(--md-mermaid-label-fg-color)' --- ...diagram... ```For
treeViewdiagrams the variables go underthemeVariables.treeView(they are not inherited from the top level):```mermaid --- config: themeVariables: treeView: labelColor: 'var(--md-mermaid-label-fg-color)' lineColor: 'var(--md-mermaid-label-fg-color)' --- treeView-beta ... ``` -
TreeView icons (Material,
mdipack): supported viadocs/javascripts/mermaid-icons.js(registered inextra_javascript). It registers themdiicon pack withmermaid.registerIconPacks()and forcessecurityLevel: "loose"(mermaid's strict sanitizer strips the<use>elements icons are rendered with — without this patch icons show as empty space). To use an icon on a node, appendicon(mdi:name)after the label, e.g."vostok" icon(mdi:folder). Common icons:mdi:folder,mdi:file,mdi:table,mdi:cube,mdi:file-document,mdi:file-image. Icons are fetched on demand from unpkg (@iconify-json/mdi), so the site must be online. -
Mermaid trims
\s-class whitespace (including U+00A0) from the start/end of labels. To pad a label to equal width, use the entity — it survives trimming as ASCII and decodes to a real space at render time.
Redirects
If you move a page, add a mapping in properdocs.yml under plugins.redirects.redirect_maps AND keep the old stub in docs/redirects/ so links from external sites don't 404. External links (forum, Telegram, bugtracker) live in redirects/ with a single external target= link in frontmatter.
Theme overrides
- Custom dir is
overrides/. Material will pick uppartials/<name>.htmlautomatically. - Extra CSS goes in
docs/stylesheets/and is registered inproperdocs.ymlunderextra_css. - Extra JS goes in
docs/javascripts/(registered underextra_javascript) — CDN scripts are listed inline in the config.
Don't
- Don't rename
properdocs.ymltomkdocs.yml— theproperdocspackage entry-point expects this name. - Don't commit
site/,.cache/,.venv/,.obsidian/workspace,*.ods#, lock files, or.vs/,.idea/,.continue/. - Don't add plugins/extensions without updating
.github/workflows/requirements.txtand theBuildsection above. - Don't create top-level dirs in
docs/; new sections belong under one of the existing nav folders, and must be added tonav:inproperdocs.yml.
VOSTOK build gallery (docs/vostok/gallery/)
The gallery is a single Material page that shows one post at a time, with the right-hand TOC acting as the post picker.
Structure:
docs/vostok/gallery/
index.md # regenerated by tools/gallery/build.py
posts/
post-NNN/ # NNN = 001, 002, ..., ordered by post date
caption.txt # raw caption + author/date/first_id metadata
photo-NN.jpg, video-NN.mp4 # media
tools/gallery/
extract.py # parses .tmp/vostok_chat_export/... → posts/
build.py # assembles index.md from posts/ folders
README.md # how to add posts manually or from Telegram export
To rebuild from a fresh Telegram export (re-running extract.py is safe, it only adds new numbered folders):
python tools/gallery/extract.py
python tools/gallery/build.py
python -m properdocs build --clean
To add a single post by hand when there is no fresh export: create posts/post-NNN/, drop the media in, write caption.txt with the template in tools/gallery/README.md, then re-run build.py. The <!-- author: ... -->, <!-- date: ... -->, <!-- first_id: ... --> lines in caption.txt are stripped at build time and feed the per-post header card + Telegram link.
The gallery uses CSS for one-post-at-a-time display: each post is <article class="gallery-post"> with an h2[id^="post-"], and the rules in docs/stylesheets/extra9.css (block «Галерея сборок VOSTOK») hide every article except the one whose H2 matches the URL fragment (via :has() + :target). A tiny inline script in index.md provides the same behavior as a fallback for browsers without :has() support.
Image prep (tools/strip_white.py)
One-shot script for the part-viewplaceholders in docs/vostok/manual/prepare.md. Takes an opaque-PNG (white background, the part on it), returns an RGBA where the background is transparent, a thin anti-aliased white stroke hugs the object's silhouette, and empty margins are cropped. Reference output: docs/vostok/manual/pics/prepare/threaded_inserts.png.
python tools/strip_white.py <path> [<path> ...]
Pipeline:
- 4-connected BFS from the 4 corners over pure-white pixels marks the outer background → alpha 0. White highlights inside the part are not reachable from the corners, so they stay opaque.
- Hole removal: connected components of remaining opaque white pixels with size ≥
MIN_HOLE_PIX(50) and fill-ratio ≥MIN_FILL_RATIO(0.5) are removed. These are the white pixels visible through holes in the part — the part's own outline around the hole is an anti-aliased gray ring that the corner BFS can't reach, so the inner white stays opaque. The size + fill-ratio heuristic distinguishes them from white highlights on the part's surface (which are small and irregular). - Two-pass chamfer-3-4 distance transform (forward TL→BR, backward BR→TL, orthogonal cost 3, diagonal cost 4, normalized by 3) gives an approximate Euclidean distance field from every transparent pixel to the nearest opaque one. Chamfer 3-4 is uniform across directions — unlike the square
MaxFilterdilation it replaces, which makes diagonal strokes √2 thicker than orthogonal ones. - Stroke region:
0 < dist ≤ STROKE_PX. Solid alpha 255 fordist ≤ STROKE_PX - AA_WIDTH, linear fade to 0 across the lastAA_WIDTHpixels. Knobs at the top of the file:STROKE_PX = 4.0,AA_WIDTH = 2.0. - Crop to the bounding box of the new opaque region.
Originals are renamed .bak1, .bak2, … (first free index) inside a .bak/ subfolder next to the source before the new file is written. The script is not idempotent — a second run treats the previous stroke as part of the foreground and grows the silhouette outward by STROKE_PX + AA_WIDTH. Original always survives in .bak/<name>.bak1. Pass --no-crop to skip the margin crop in step 5, and --square to pad the result to a square canvas (transparent margins, content centered) for use in grid cards layouts where the aspect ratio would otherwise misalign tiles.