diff --git a/CHANGELOG.ar.md b/CHANGELOG.ar.md index b860967..c135848 100644 --- a/CHANGELOG.ar.md +++ b/CHANGELOG.ar.md @@ -1,5 +1,20 @@ # سجل التغييرات +## 0.30.0 +- **اختر أزرار شريط الأدوات** (**الإعدادات ← شريط الأدوات**، أو **☰ ← تخصيص شريط الأدوات…** أو النقر + بالزر الأيمن على شريط الأدوات): أخفِ الأزرار التي لا تستخدمها: الرئيسية، فتح، فحص، العرض، قياس، لقطة + شاشة، السمة والإعدادات. وتبقى دائماً القائمة **☰** والفتح في برنامج التقطيع وتصدير STL واللوحة + وملاءمة. لا يتغيّر الترتيب أبداً، وتحتفظ الأزرار المخفية باختصاراتها، وصارت القائمة **☰** تضم أيضاً + فحص والعرض وقياس وقائمة السمة. [شاهده أثناء العمل](https://sevagjb.github.io/PrintView3D/ar/use.html#toolbar-buttons). +- **أزرار عرض أقصر** (في الصفحة نفسها): اعرض اللوحة وفحص والعرض وقياس وملاءمة بصيغة *اللوحة (P)* أو + *اللوحة* أو *P* أو الأيقونات فقط، واضممها في مجموعة واحدة متصلة إن شئت. يظهر كل تغيير فوراً؛ **موافق** + يحتفظ به و**إلغاء** يعيد ما كان. +- **تضيء أزرار شريط الأدوات تحت الماوس**: يمتلئ المربع بلون السمة الرئيسي (الكهرماني في Ember، والفيروزي + في السمات الأخرى)، مثل مكعب العرض. وأصبحت أيقونة ترس الإعدادات برتقالية في Ember، مثل لقطة الشاشة + والسمة بجانبها. +- **الموقع**: تتبع لقطات الشاشة سمة الموقع (فاتح أو داكن أو Ember). +- **تحت الغطاء**: أُزيلت شيفرة غير مستخدمة. + ## 0.29.1 - **مُصلح**: في **فحص النموذج**، كانت الأجزاء المتدلية المخفية داخل النموذج أو تحته تظهر من خلاله، وأكثر من ذلك على بعض بطاقات الرسوميات. ومن الأسفل تُلوَّن تماماً كما كانت. diff --git a/CHANGELOG.md b/CHANGELOG.md index 62db203..05556a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.30.0 +- **Choose your toolbar** (**Settings → Toolbar**, also **☰ → Customize toolbar…** or a right-click on + the toolbar): hide the buttons you don't use: Home, Open, Check, View, Measure, Screenshot, Theme + and Settings. The **☰** menu, Open in slicer, Export STL, Plate and Fit always stay. The order never + changes, hidden buttons keep their keys, and the **☰** menu now also has Check, View, Measure and + the Theme menu. [See it in action](https://sevagjb.github.io/PrintView3D/use.html#toolbar-buttons). +- **Shorter view buttons** (same page): show Plate, Check, View, Measure and Fit as *Plate (P)*, + *Plate*, *P* or icons only, and join them into one connected group if you like. Every change shows + right away; OK keeps it, Cancel goes back. +- **Toolbar buttons light up under the mouse**: the tile fills with the theme's main colour (amber in + Ember, teal in the other themes), like the view cube. The Settings gear is now orange in Ember, like + Screenshot and Theme beside it. +- **Website**: the screenshots follow the website's theme (Light, Dark or Ember). +- **Under the hood**: unused code removed. + ## 0.29.1 - **Fixed**: in **Check model**, overhangs hidden inside or under the model could show through it, more on some graphics cards. From below they are coloured exactly as before. diff --git a/CHANGELOG.nl.md b/CHANGELOG.nl.md index d3781b4..bd9f90d 100644 --- a/CHANGELOG.nl.md +++ b/CHANGELOG.nl.md @@ -1,5 +1,21 @@ # Changelog +## 0.30.0 +- **Kies je werkbalk** (**Instellingen → Werkbalk**, ook **☰ → Werkbalk aanpassen…** of klik met rechts + op de werkbalk): verberg de knoppen die je niet gebruikt: Start, Openen, Controleren, Weergave, + Meten, Schermafbeelding, Thema en Instellingen. Het **☰**-menu, Openen in slicer, STL exporteren, + Plaat en Passend blijven altijd staan. De volgorde verandert nooit, verborgen knoppen houden hun + toets, en het **☰**-menu heeft nu ook Controleren, Weergave, Meten en het themamenu. + [Bekijk hoe het werkt](https://sevagjb.github.io/PrintView3D/nl/use.html#toolbar-buttons). +- **Kortere weergaveknoppen** (zelfde pagina): toon Plaat, Controleren, Weergave, Meten en Passend als + *Plaat (P)*, *Plaat*, *P* of alleen pictogrammen, en voeg ze desgewenst samen tot één aaneengesloten + groep. Elke wijziging is meteen te zien; **OK** houdt hem, **Annuleren** gaat terug. +- **Werkbalkknoppen lichten op onder de muis**: het vlak vult zich met de hoofdkleur van het thema + (amber in Ember, cyaan in de andere thema's), net als de weergavekubus. Het tandwiel van Instellingen + is nu oranje in Ember, net als Schermafbeelding en Thema ernaast. +- **Website**: de schermafbeeldingen volgen het thema van de website (Licht, Donker of Ember). +- **Onder de motorkap**: ongebruikte code verwijderd. + ## 0.29.1 - **Opgelost**: bij **Model controleren** konden overhangen die in of onder het model verborgen zitten erdoorheen zichtbaar zijn, op sommige grafische kaarten meer dan op andere. Van onderen worden ze diff --git a/README.md b/README.md index ca5df2c..aee22ba 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ without exporting an STL first. | **One window** | A file you double-click while PrintView3D is open is shown in that window, instead of opening another one. Prefer a window per file? **Settings → Files → Open each file in a new window**. | | **Look inside** | **View** → wireframe, X-ray (see-through), clip from the top with a height slider and a cut that faces you as you turn the view, on their own or both at once, with the cut-away part as a see-through ghost, to check meshes, hidden parts and hollow walls. **Measure** distances, angles and circles. [Details](#view-wireframe-x-ray-and-clip). | | **Check before printing** | **Check** → overhangs, thin walls, holes, tangled (non-manifold) edges, inside-out faces and loose parts, coloured on the model with a count for each. [Details](#check-before-printing). | +| **Your toolbar** | Hide the buttons you don't use, shorten the view buttons to a name, a key or an icon, and join them into one group (**Settings → Toolbar**). Hidden buttons keep their keys and stay in the **☰** menu. [Details](#toolbar-buttons). | | **Comfortable to use** | Slicer-style camera, drag & drop, recent files, open a whole folder, screenshots, millimetres or inches, and keyboard shortcuts you can change. | @@ -586,6 +587,28 @@ effect itself and shows a plain colour while the window is not active, and switc *Transparency effects* off in Windows Settings or in battery saver. Windows 10 and other systems keep the normal look. +### Toolbar buttons + +**Settings → Toolbar** (also **☰ → Customize toolbar…**, or right-click the toolbar) chooses which +buttons the toolbar shows, for a calmer toolbar if you don't use every feature: + +- **Can be hidden:** Home, Open, Check, View, Measure, Screenshot, Theme and Settings. +- **Always shown:** the **☰** menu, Open in slicer, Export STL (3MF and CAD files), Plate and Fit. +- **The order never changes.** A hidden button only leaves the toolbar; Plate stays the first of the + view buttons and Fit the last. +- **Nothing gets lost.** A hidden button keeps its key (`C`, `V`, `M`, `Ctrl+W`, …), and the **☰** + menu has everything, including Check, View, Measure and the Theme menu. + +Under **Look** you choose how the view buttons (Plate, Check, View, Measure and Fit) are labelled: +**icon, name and key** (*Plate (P)*, the default), **icon and name** (*Plate*), **icon and key** +(*P*) or **icon only** (the tooltip names it). **Join the view buttons into one group** draws them +as one connected bar. + +![Settings → Toolbar: buttons switched off, shorter labels and a joined group, shown live on the toolbar](docs/assets/toolbar-settings.gif) + +Every change shows in the window, and in the picture at the top of the page, right away: **OK** +keeps it, **Cancel** goes back. **Reset to defaults** shows every button again. + ### Updates **Settings → Updates** shows your version and has **Check for updates now**. You can also switch on diff --git a/docs/assets/extra.css b/docs/assets/extra.css index cec1cb5..f3acc5d 100644 --- a/docs/assets/extra.css +++ b/docs/assets/extra.css @@ -97,3 +97,12 @@ [data-md-color-scheme="ember"] .md-nav__link--active code { color: var(--pv-ember-teal-deep); } /* Tables: header row in the window colour. */ [data-md-color-scheme="ember"] .md-typeset table:not([class]) th { background-color: var(--pv-ember-window); } + +/* ---------------------------------------------------------------- pictures that match the theme + tools/mkdocs_theme_images.py puts a Light and an Ember version beside each screenshot that has + them: only the one for the chosen theme shows (Dark: the original picture). */ +img.pv-theme-light, img.pv-theme-ember { display: none; } +[data-md-color-scheme="default"] img.pv-theme-dark, +[data-md-color-scheme="ember"] img.pv-theme-dark { display: none; } +[data-md-color-scheme="default"] img.pv-theme-light, +[data-md-color-scheme="ember"] img.pv-theme-ember { display: inline; } diff --git a/docs/assets/inside/check--ember.webp b/docs/assets/inside/check--ember.webp new file mode 100644 index 0000000..f5fd920 Binary files /dev/null and b/docs/assets/inside/check--ember.webp differ diff --git a/docs/assets/inside/check--light.webp b/docs/assets/inside/check--light.webp new file mode 100644 index 0000000..866a5ae Binary files /dev/null and b/docs/assets/inside/check--light.webp differ diff --git a/docs/assets/inside/check-ar--ember.webp b/docs/assets/inside/check-ar--ember.webp new file mode 100644 index 0000000..55686ad Binary files /dev/null and b/docs/assets/inside/check-ar--ember.webp differ diff --git a/docs/assets/inside/check-ar--light.webp b/docs/assets/inside/check-ar--light.webp new file mode 100644 index 0000000..da82218 Binary files /dev/null and b/docs/assets/inside/check-ar--light.webp differ diff --git a/docs/assets/inside/check-nl--ember.webp b/docs/assets/inside/check-nl--ember.webp new file mode 100644 index 0000000..6908a71 Binary files /dev/null and b/docs/assets/inside/check-nl--ember.webp differ diff --git a/docs/assets/inside/check-nl--light.webp b/docs/assets/inside/check-nl--light.webp new file mode 100644 index 0000000..d90dde2 Binary files /dev/null and b/docs/assets/inside/check-nl--light.webp differ diff --git a/docs/assets/inside/clip-facing--ember.webp b/docs/assets/inside/clip-facing--ember.webp new file mode 100644 index 0000000..4683a6a Binary files /dev/null and b/docs/assets/inside/clip-facing--ember.webp differ diff --git a/docs/assets/inside/clip-facing--light.webp b/docs/assets/inside/clip-facing--light.webp new file mode 100644 index 0000000..d349477 Binary files /dev/null and b/docs/assets/inside/clip-facing--light.webp differ diff --git a/docs/assets/inside/clip-facing-ar--ember.webp b/docs/assets/inside/clip-facing-ar--ember.webp new file mode 100644 index 0000000..339cd7e Binary files /dev/null and b/docs/assets/inside/clip-facing-ar--ember.webp differ diff --git a/docs/assets/inside/clip-facing-ar--light.webp b/docs/assets/inside/clip-facing-ar--light.webp new file mode 100644 index 0000000..faa1080 Binary files /dev/null and b/docs/assets/inside/clip-facing-ar--light.webp differ diff --git a/docs/assets/inside/clip-facing-nl--ember.webp b/docs/assets/inside/clip-facing-nl--ember.webp new file mode 100644 index 0000000..ce7b80d Binary files /dev/null and b/docs/assets/inside/clip-facing-nl--ember.webp differ diff --git a/docs/assets/inside/clip-facing-nl--light.webp b/docs/assets/inside/clip-facing-nl--light.webp new file mode 100644 index 0000000..58b76cc Binary files /dev/null and b/docs/assets/inside/clip-facing-nl--light.webp differ diff --git a/docs/assets/inside/colors--ember.webp b/docs/assets/inside/colors--ember.webp new file mode 100644 index 0000000..d2a0a95 Binary files /dev/null and b/docs/assets/inside/colors--ember.webp differ diff --git a/docs/assets/inside/colors--light.webp b/docs/assets/inside/colors--light.webp new file mode 100644 index 0000000..ea12ce2 Binary files /dev/null and b/docs/assets/inside/colors--light.webp differ diff --git a/docs/assets/inside/colors-ar--ember.webp b/docs/assets/inside/colors-ar--ember.webp new file mode 100644 index 0000000..7d2608b Binary files /dev/null and b/docs/assets/inside/colors-ar--ember.webp differ diff --git a/docs/assets/inside/colors-ar--light.webp b/docs/assets/inside/colors-ar--light.webp new file mode 100644 index 0000000..dc9c9c8 Binary files /dev/null and b/docs/assets/inside/colors-ar--light.webp differ diff --git a/docs/assets/inside/colors-nl--ember.webp b/docs/assets/inside/colors-nl--ember.webp new file mode 100644 index 0000000..8b697d0 Binary files /dev/null and b/docs/assets/inside/colors-nl--ember.webp differ diff --git a/docs/assets/inside/colors-nl--light.webp b/docs/assets/inside/colors-nl--light.webp new file mode 100644 index 0000000..88f15e9 Binary files /dev/null and b/docs/assets/inside/colors-nl--light.webp differ diff --git a/docs/assets/inside/grid-numbers--ember.webp b/docs/assets/inside/grid-numbers--ember.webp new file mode 100644 index 0000000..246269f Binary files /dev/null and b/docs/assets/inside/grid-numbers--ember.webp differ diff --git a/docs/assets/inside/grid-numbers--light.webp b/docs/assets/inside/grid-numbers--light.webp new file mode 100644 index 0000000..1eba7f3 Binary files /dev/null and b/docs/assets/inside/grid-numbers--light.webp differ diff --git a/docs/assets/inside/measure--ember.webp b/docs/assets/inside/measure--ember.webp new file mode 100644 index 0000000..05b7a9a Binary files /dev/null and b/docs/assets/inside/measure--ember.webp differ diff --git a/docs/assets/inside/measure--light.webp b/docs/assets/inside/measure--light.webp new file mode 100644 index 0000000..3e8b25a Binary files /dev/null and b/docs/assets/inside/measure--light.webp differ diff --git a/docs/assets/inside/measure-ar--ember.webp b/docs/assets/inside/measure-ar--ember.webp new file mode 100644 index 0000000..abaeb2c Binary files /dev/null and b/docs/assets/inside/measure-ar--ember.webp differ diff --git a/docs/assets/inside/measure-ar--light.webp b/docs/assets/inside/measure-ar--light.webp new file mode 100644 index 0000000..3951267 Binary files /dev/null and b/docs/assets/inside/measure-ar--light.webp differ diff --git a/docs/assets/inside/measure-cut--ember.webp b/docs/assets/inside/measure-cut--ember.webp new file mode 100644 index 0000000..9953dc8 Binary files /dev/null and b/docs/assets/inside/measure-cut--ember.webp differ diff --git a/docs/assets/inside/measure-cut--light.webp b/docs/assets/inside/measure-cut--light.webp new file mode 100644 index 0000000..1a5d67c Binary files /dev/null and b/docs/assets/inside/measure-cut--light.webp differ diff --git a/docs/assets/inside/measure-cut-ar--ember.webp b/docs/assets/inside/measure-cut-ar--ember.webp new file mode 100644 index 0000000..4f39f01 Binary files /dev/null and b/docs/assets/inside/measure-cut-ar--ember.webp differ diff --git a/docs/assets/inside/measure-cut-ar--light.webp b/docs/assets/inside/measure-cut-ar--light.webp new file mode 100644 index 0000000..1d58b5f Binary files /dev/null and b/docs/assets/inside/measure-cut-ar--light.webp differ diff --git a/docs/assets/inside/measure-cut-nl--ember.webp b/docs/assets/inside/measure-cut-nl--ember.webp new file mode 100644 index 0000000..5d66359 Binary files /dev/null and b/docs/assets/inside/measure-cut-nl--ember.webp differ diff --git a/docs/assets/inside/measure-cut-nl--light.webp b/docs/assets/inside/measure-cut-nl--light.webp new file mode 100644 index 0000000..ae5be52 Binary files /dev/null and b/docs/assets/inside/measure-cut-nl--light.webp differ diff --git a/docs/assets/inside/measure-nl--ember.webp b/docs/assets/inside/measure-nl--ember.webp new file mode 100644 index 0000000..3c63a37 Binary files /dev/null and b/docs/assets/inside/measure-nl--ember.webp differ diff --git a/docs/assets/inside/measure-nl--light.webp b/docs/assets/inside/measure-nl--light.webp new file mode 100644 index 0000000..79569ef Binary files /dev/null and b/docs/assets/inside/measure-nl--light.webp differ diff --git a/docs/assets/inside/objects--ember.webp b/docs/assets/inside/objects--ember.webp new file mode 100644 index 0000000..4e0cf45 Binary files /dev/null and b/docs/assets/inside/objects--ember.webp differ diff --git a/docs/assets/inside/objects--light.webp b/docs/assets/inside/objects--light.webp new file mode 100644 index 0000000..1c46f56 Binary files /dev/null and b/docs/assets/inside/objects--light.webp differ diff --git a/docs/assets/inside/objects-ar--ember.webp b/docs/assets/inside/objects-ar--ember.webp new file mode 100644 index 0000000..ebb6f94 Binary files /dev/null and b/docs/assets/inside/objects-ar--ember.webp differ diff --git a/docs/assets/inside/objects-ar--light.webp b/docs/assets/inside/objects-ar--light.webp new file mode 100644 index 0000000..355a487 Binary files /dev/null and b/docs/assets/inside/objects-ar--light.webp differ diff --git a/docs/assets/inside/objects-nl--ember.webp b/docs/assets/inside/objects-nl--ember.webp new file mode 100644 index 0000000..b7979d6 Binary files /dev/null and b/docs/assets/inside/objects-nl--ember.webp differ diff --git a/docs/assets/inside/objects-nl--light.webp b/docs/assets/inside/objects-nl--light.webp new file mode 100644 index 0000000..14f6c20 Binary files /dev/null and b/docs/assets/inside/objects-nl--light.webp differ diff --git a/docs/assets/inside/xray-clip--ember.webp b/docs/assets/inside/xray-clip--ember.webp new file mode 100644 index 0000000..e76d307 Binary files /dev/null and b/docs/assets/inside/xray-clip--ember.webp differ diff --git a/docs/assets/inside/xray-clip--light.webp b/docs/assets/inside/xray-clip--light.webp new file mode 100644 index 0000000..8e6f23a Binary files /dev/null and b/docs/assets/inside/xray-clip--light.webp differ diff --git a/docs/assets/inside/xray-clip-ar--ember.webp b/docs/assets/inside/xray-clip-ar--ember.webp new file mode 100644 index 0000000..917fe4e Binary files /dev/null and b/docs/assets/inside/xray-clip-ar--ember.webp differ diff --git a/docs/assets/inside/xray-clip-ar--light.webp b/docs/assets/inside/xray-clip-ar--light.webp new file mode 100644 index 0000000..0a9f13e Binary files /dev/null and b/docs/assets/inside/xray-clip-ar--light.webp differ diff --git a/docs/assets/inside/xray-clip-nl--ember.webp b/docs/assets/inside/xray-clip-nl--ember.webp new file mode 100644 index 0000000..e3bc0ab Binary files /dev/null and b/docs/assets/inside/xray-clip-nl--ember.webp differ diff --git a/docs/assets/inside/xray-clip-nl--light.webp b/docs/assets/inside/xray-clip-nl--light.webp new file mode 100644 index 0000000..280028d Binary files /dev/null and b/docs/assets/inside/xray-clip-nl--light.webp differ diff --git a/docs/assets/toolbar-settings.gif b/docs/assets/toolbar-settings.gif new file mode 100644 index 0000000..ac081cd Binary files /dev/null and b/docs/assets/toolbar-settings.gif differ diff --git a/docs/index.ar.md b/docs/index.ar.md index ce63364..e46e64d 100644 --- a/docs/index.ar.md +++ b/docs/index.ar.md @@ -21,7 +21,13 @@ hide: بنقرة واحدة. تصمّم في FreeCAD؟ افتح ملف `.FCStd` وأرسله إلى برنامج التقطيع كنموذج مباشرةً، دون تصدير ملف STL أولًا. -## جديد: الفحص والقياس والنظر داخل نماذجك +## جديد: شريط أدواتك، والفحص والقياس والنظر داخل نماذجك + +**جديد في 0.30: شريط أدواتك أنت.** أخفِ الأزرار التي لا تستخدمها، واختصر أزرار العرض إلى اسم أو +اختصار أو أيقونة، واضممها في مجموعة واحدة. تحتفظ الأزرار المخفية باختصاراتها، ويبقى كل شيء في القائمة +**☰**. [كيف يعمل](use.md#toolbar-buttons) + +![الإعدادات ← شريط الأدوات: أزرار مخفية وتسميات أقصر ومجموعة متصلة، تظهر فوراً على شريط الأدوات](assets/toolbar-settings.gif) **جديد في 0.29: الفحص قبل الطباعة.** نقرة واحدة تلوّن ما قد يسبب مشكلات عند الطباعة: الأجزاء المتدلية، والجدران الأرق من فوهتك، والثقوب، والحواف المتشابكة، والأوجه المعكوسة، والأجزاء المنفصلة، مع @@ -63,6 +69,7 @@ hide: | **الصور المصغّرة** | يعرض مستكشف الملفات في Windows معاينات لملفات 3MF وSTL وG-code وFreeCAD (تُثبَّت مع PrintView3D، اختيارية). وعلى macOS (تجريبي) يعرض Finder صوراً مصغّرة لملفات 3MF وSTL وG-code وFreeCAD وOBJ وPLY، ويعرض **Quick Look** (مفتاح المسافة) نماذج STL و3MF وOBJ وPLY بشكل ثلاثي الأبعاد. [التفاصيل](install.md#quick-look). | | **التحديثات** | **التحقق من التحديثات** من الإعدادات، أو مرة يوميًا إذا سمحت بذلك. على Windows تقوم نقرة واحدة بتنزيل الإصدار الجديد والتحقق منه وتثبيته. يظهر بجانب رقم الإصدار في الزاوية نقطة حمراء عند توفر تحديث. [ما الذي يُرسَل](use.md#updates-and-privacy). | | **نافذة واحدة** | الملف الذي تنقر عليه نقرًا مزدوجًا أثناء فتح PrintView3D يظهر في النافذة نفسها بدل فتح نافذة أخرى. تفضّل نافذة لكل ملف؟ **الإعدادات ← الملفات ← فتح كل ملف في نافذة جديدة**. | +| **شريط أدواتك** | أخفِ الأزرار التي لا تستخدمها، واختصر أزرار العرض إلى اسم أو اختصار أو أيقونة، واضممها في مجموعة واحدة (**الإعدادات ← شريط الأدوات**). تحتفظ الأزرار المخفية باختصاراتها وتبقى في القائمة **☰**. [التفاصيل](use.md#toolbar-buttons). | | **سهل الاستخدام** | كاميرا بأسلوب برامج التقطيع، والسحب والإفلات، والملفات الأخيرة، ولقطات الشاشة. |
diff --git a/docs/index.md b/docs/index.md index 74efc63..f22d7d9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,7 +21,13 @@ file to Creality Print, OrcaSlicer, Bambu Studio, PrusaSlicer, Cura and others w Designing in FreeCAD? Open the `.FCStd` file and send it to your slicer as a model directly, without exporting an STL first. -## New: check, measure and look inside your models +## New: your own toolbar, check, measure and look inside + +**New in 0.30: your own toolbar.** Hide the buttons you don't use, shorten the view buttons to a +name, a key or an icon, and join them into one group. Hidden buttons keep their keys, and everything +stays in the **☰** menu. [How it works](use.md#toolbar-buttons) + +![Settings → Toolbar: buttons switched off, shorter labels and a joined group, shown live on the toolbar](assets/toolbar-settings.gif) **New in 0.29: Check before printing.** One click colours what may give trouble when you print: overhangs, walls thinner than your nozzle, holes, tangled edges, inside-out faces and loose parts, @@ -65,6 +71,7 @@ each object a colour of your own, and see the size of every object. | **Thumbnails** | Windows File Explorer shows previews of 3MF, STL, G-code and FreeCAD files (installed with PrintView3D, optional). On macOS (beta), Finder shows thumbnails of 3MF, STL, G-code, FreeCAD, OBJ and PLY files, and **Quick Look** (space bar) shows STL, 3MF, OBJ and PLY models in 3D. [Details](install.md#quick-look). | | **Updates** | **Check for updates** in Settings, or once a day if you allow it. On Windows one click downloads, checks and installs the new version. The version in the corner gets a red dot when an update is ready. [What is sent](use.md#updates-and-privacy). | | **One window** | A file you double-click while PrintView3D is open is shown in that window, instead of opening another one. Prefer a window per file? **Settings → Files → Open each file in a new window**. | +| **Your toolbar** | Hide the buttons you don't use, shorten the view buttons to a name, a key or an icon, and join them into one group (**Settings → Toolbar**). Hidden buttons keep their keys and stay in the **☰** menu. [Details](use.md#toolbar-buttons). | | **Comfortable to use** | Slicer-style camera, drag & drop, recent files, screenshots. |
diff --git a/docs/index.nl.md b/docs/index.nl.md index b980fe7..52e7218 100644 --- a/docs/index.nl.md +++ b/docs/index.nl.md @@ -21,7 +21,13 @@ printtijd en het filamentverbruik, en stuurt het bestand met één klik naar Cre Bambu Studio, PrusaSlicer, Cura en andere. Ontwerp je in FreeCAD? Open het `.FCStd`-bestand en stuur het direct als model naar je slicer, zonder eerst een STL te exporteren. -## Nieuw: controleren, meten en in je modellen kijken +## Nieuw: je eigen werkbalk, controleren, meten en in je modellen kijken + +**Nieuw in 0.30: je eigen werkbalk.** Verberg de knoppen die je niet gebruikt, maak de weergaveknoppen +korter tot een naam, een toets of een pictogram, en voeg ze samen tot één groep. Verborgen knoppen +houden hun toets, en alles blijft in het **☰**-menu. [Hoe het werkt](use.md#toolbar-buttons) + +![Instellingen → Werkbalk: knoppen uit, kortere namen en een samengevoegde groep, meteen te zien op de werkbalk](assets/toolbar-settings.gif) **Nieuw in 0.29: Controleren voor het printen.** Eén klik kleurt wat bij het printen problemen kan geven: overhangen, wanden dunner dan je nozzle, gaten, verwarde randen, binnenstebuiten gekeerde @@ -66,6 +72,7 @@ doorzichtige schim. Maak het doorzichtig, geef elk object een eigen kleur en zie | **Miniaturen** | Windows Verkenner toont voorbeelden van 3MF-, STL-, G-code- en FreeCAD-bestanden (geïnstalleerd met PrintView3D, optioneel). Op macOS (bèta) toont de Finder miniaturen van 3MF-, STL-, G-code-, FreeCAD-, OBJ- en PLY-bestanden, en **Quick Look** (spatiebalk) toont STL-, 3MF-, OBJ- en PLY-modellen in 3D. [Details](install.md#quick-look). | | **Updates** | **Controleren op updates** in Instellingen, of één keer per dag als je dat toestaat. Op Windows downloadt, controleert en installeert één klik de nieuwe versie. De versie in de hoek krijgt een rode stip als er een update klaarstaat. [Wat er verstuurd wordt](use.md#updates-and-privacy). | | **Eén venster** | Een bestand waarop je dubbelklikt terwijl PrintView3D open is, verschijnt in dat venster in plaats van in een nieuw venster. Liever een venster per bestand? **Instellingen → Bestanden → Elk bestand in een nieuw venster openen**. | +| **Je eigen werkbalk** | Verberg de knoppen die je niet gebruikt, maak de weergaveknoppen korter tot een naam, een toets of een pictogram, en voeg ze samen tot één groep (**Instellingen → Werkbalk**). Verborgen knoppen houden hun toets en blijven in het **☰**-menu. [Details](use.md#toolbar-buttons). | | **Prettig in gebruik** | Camera zoals in een slicer, slepen en neerzetten, recente bestanden, schermafbeeldingen. |
diff --git a/docs/screenshot-3mf--ember.webp b/docs/screenshot-3mf--ember.webp new file mode 100644 index 0000000..9307792 Binary files /dev/null and b/docs/screenshot-3mf--ember.webp differ diff --git a/docs/screenshot-3mf--light.webp b/docs/screenshot-3mf--light.webp new file mode 100644 index 0000000..6b26ece Binary files /dev/null and b/docs/screenshot-3mf--light.webp differ diff --git a/docs/screenshot-3mf.png b/docs/screenshot-3mf.png index 0fdc01c..f9fb491 100644 Binary files a/docs/screenshot-3mf.png and b/docs/screenshot-3mf.png differ diff --git a/docs/screenshot-arabic--ember.webp b/docs/screenshot-arabic--ember.webp new file mode 100644 index 0000000..66b7bdd Binary files /dev/null and b/docs/screenshot-arabic--ember.webp differ diff --git a/docs/screenshot-arabic--light.webp b/docs/screenshot-arabic--light.webp new file mode 100644 index 0000000..b505472 Binary files /dev/null and b/docs/screenshot-arabic--light.webp differ diff --git a/docs/screenshot-arabic.png b/docs/screenshot-arabic.png index 0226686..35cf205 100644 Binary files a/docs/screenshot-arabic.png and b/docs/screenshot-arabic.png differ diff --git a/docs/screenshot-freecad-preview.png b/docs/screenshot-freecad-preview.png index 5bf9345..1affb93 100644 Binary files a/docs/screenshot-freecad-preview.png and b/docs/screenshot-freecad-preview.png differ diff --git a/docs/screenshot-gcode--ember.webp b/docs/screenshot-gcode--ember.webp new file mode 100644 index 0000000..25ee6d0 Binary files /dev/null and b/docs/screenshot-gcode--ember.webp differ diff --git a/docs/screenshot-gcode--light.webp b/docs/screenshot-gcode--light.webp new file mode 100644 index 0000000..a489613 Binary files /dev/null and b/docs/screenshot-gcode--light.webp differ diff --git a/docs/screenshot-gcode.png b/docs/screenshot-gcode.png index 4d4793f..3bb55f4 100644 Binary files a/docs/screenshot-gcode.png and b/docs/screenshot-gcode.png differ diff --git a/docs/screenshot-layer--ember.webp b/docs/screenshot-layer--ember.webp new file mode 100644 index 0000000..4ce8206 Binary files /dev/null and b/docs/screenshot-layer--ember.webp differ diff --git a/docs/screenshot-layer--light.webp b/docs/screenshot-layer--light.webp new file mode 100644 index 0000000..19170bc Binary files /dev/null and b/docs/screenshot-layer--light.webp differ diff --git a/docs/screenshot-layer.png b/docs/screenshot-layer.png index 76c95d5..0c1df9d 100644 Binary files a/docs/screenshot-layer.png and b/docs/screenshot-layer.png differ diff --git a/docs/screenshot-model--ember.webp b/docs/screenshot-model--ember.webp new file mode 100644 index 0000000..3bb5c84 Binary files /dev/null and b/docs/screenshot-model--ember.webp differ diff --git a/docs/screenshot-model--light.webp b/docs/screenshot-model--light.webp new file mode 100644 index 0000000..53d779a Binary files /dev/null and b/docs/screenshot-model--light.webp differ diff --git a/docs/screenshot-model.png b/docs/screenshot-model.png index 22cc0a1..0bf1e54 100644 Binary files a/docs/screenshot-model.png and b/docs/screenshot-model.png differ diff --git a/docs/screenshot-settings--ember.webp b/docs/screenshot-settings--ember.webp new file mode 100644 index 0000000..07a0173 Binary files /dev/null and b/docs/screenshot-settings--ember.webp differ diff --git a/docs/screenshot-settings--light.webp b/docs/screenshot-settings--light.webp new file mode 100644 index 0000000..2218a32 Binary files /dev/null and b/docs/screenshot-settings--light.webp differ diff --git a/docs/screenshot-settings.png b/docs/screenshot-settings.png index a0de989..38a0b93 100644 Binary files a/docs/screenshot-settings.png and b/docs/screenshot-settings.png differ diff --git a/docs/screenshot-slicer-menu--ember.webp b/docs/screenshot-slicer-menu--ember.webp new file mode 100644 index 0000000..1e45776 Binary files /dev/null and b/docs/screenshot-slicer-menu--ember.webp differ diff --git a/docs/screenshot-slicer-menu--light.webp b/docs/screenshot-slicer-menu--light.webp new file mode 100644 index 0000000..2dcbe90 Binary files /dev/null and b/docs/screenshot-slicer-menu--light.webp differ diff --git a/docs/screenshot-slicer-menu.png b/docs/screenshot-slicer-menu.png index cf53c19..753f160 100644 Binary files a/docs/screenshot-slicer-menu.png and b/docs/screenshot-slicer-menu.png differ diff --git a/docs/screenshot-theme-graphite.png b/docs/screenshot-theme-graphite.png index a15e2a0..714b3f8 100644 Binary files a/docs/screenshot-theme-graphite.png and b/docs/screenshot-theme-graphite.png differ diff --git a/docs/screenshot-theme-gray.png b/docs/screenshot-theme-gray.png index acebd62..68dd943 100644 Binary files a/docs/screenshot-theme-gray.png and b/docs/screenshot-theme-gray.png differ diff --git a/docs/screenshot-theme-light.png b/docs/screenshot-theme-light.png index ef31cae..9e594bc 100644 Binary files a/docs/screenshot-theme-light.png and b/docs/screenshot-theme-light.png differ diff --git a/docs/screenshot-theme-menu.png b/docs/screenshot-theme-menu.png index 9acb18d..f798199 100644 Binary files a/docs/screenshot-theme-menu.png and b/docs/screenshot-theme-menu.png differ diff --git a/docs/screenshot-themes.png b/docs/screenshot-themes.png index 71c2cf8..d199a46 100644 Binary files a/docs/screenshot-themes.png and b/docs/screenshot-themes.png differ diff --git a/docs/screenshot-updates--ember.webp b/docs/screenshot-updates--ember.webp new file mode 100644 index 0000000..bb4f05d Binary files /dev/null and b/docs/screenshot-updates--ember.webp differ diff --git a/docs/screenshot-updates--light.webp b/docs/screenshot-updates--light.webp new file mode 100644 index 0000000..14c6d90 Binary files /dev/null and b/docs/screenshot-updates--light.webp differ diff --git a/docs/screenshot-updates.png b/docs/screenshot-updates.png index 285187b..01f20c1 100644 Binary files a/docs/screenshot-updates.png and b/docs/screenshot-updates.png differ diff --git a/docs/use.ar.md b/docs/use.ar.md index 546ac9d..bad129a 100644 --- a/docs/use.ar.md +++ b/docs/use.ar.md @@ -103,7 +103,8 @@ الماوس الأيمن يعيد لون الملف. لا تُحفظ الألوان. - **أرقام الشبكة**: **الإعدادات ← العارض ← إظهار أرقام المقياس على الشبكة** يكتب المليمترات على طول لوحة الطباعة، بقدر ما يتسع عند التكبير الذي تنظر به. -- **الإعدادات** متاحة أيضاً من أيقونة الترس في طرف شريط الأدوات، بجانب أيقونة السمة. +- **الإعدادات** متاحة أيضاً من أيقونة الترس في طرف شريط الأدوات، بجانب أيقونة السمة + (إلا إذا أخفيتها: انظر [أزرار شريط الأدوات](#toolbar-buttons)). المزيد مع الصور: [انظر داخل نماذجك](inspect.md). من تطوير [@covrig](https://github.com/covrig). @@ -229,6 +230,28 @@ PrintView3D: **إعادة التشغيل الآن** (يُغلَق ويُفتح بنفسه ويعرض لونًا عاديًا عندما لا تكون النافذة نشطة، ويوقفه عند إيقاف *Transparency effects* في إعدادات Windows أو في وضع توفير البطارية. يحتفظ Windows 10 والأنظمة الأخرى بالشكل العادي. +## أزرار شريط الأدوات {#toolbar-buttons} + +تختار **الإعدادات ← شريط الأدوات** (أو **☰ ← تخصيص شريط الأدوات…**، أو النقر بالزر الأيمن على شريط +الأدوات) الأزرار التي يعرضها شريط الأدوات، لشريط أهدأ إن لم تكن تستخدم كل الميزات: + +- **يمكن إخفاؤها:** الرئيسية، فتح، فحص، العرض، قياس، لقطة شاشة، السمة والإعدادات. +- **ظاهرة دائماً:** القائمة **☰**، الفتح في برنامج التقطيع، تصدير STL (لملفات 3MF وCAD)، اللوحة وملاءمة. +- **لا يتغيّر الترتيب أبداً.** الزر المخفي يغادر شريط الأدوات فقط؛ تبقى اللوحة أول أزرار العرض + وملاءمة آخرها. +- **لا يضيع شيء.** يحتفظ الزر المخفي باختصاره (`C` و`V` و`M` و`Ctrl+W`…)، وفي القائمة **☰** كل + شيء، ومنه فحص والعرض وقياس وقائمة السمة. + +تحت **الشكل** تختار كيف تُكتب أزرار العرض (اللوحة، فحص، العرض، قياس وملاءمة): **الأيقونة والاسم +والاختصار** (*اللوحة (P)*، الافتراضي)، **الأيقونة والاسم** (*اللوحة*)، **الأيقونة والاختصار** (*P*) +أو **الأيقونة فقط** (يذكر التلميح اسمها). ويرسمها **ضمّ أزرار العرض في مجموعة واحدة** شريطاً واحداً +متصلاً. + +![الإعدادات ← شريط الأدوات: أزرار مخفية وتسميات أقصر ومجموعة متصلة، تظهر فوراً على شريط الأدوات](assets/toolbar-settings.gif) + +يظهر كل تغيير فوراً في النافذة وفي الصورة أعلى الصفحة: **موافق** يحتفظ به و**إلغاء** يعيد ما كان. +و**استعادة الافتراضيات** تُظهر كل الأزرار من جديد. + ## التحديثات {#updates} **الإعدادات ← التحديثات** تعرض إصدارك وفيها **التحقق من التحديثات الآن**. يمكنك أيضًا تفعيل diff --git a/docs/use.md b/docs/use.md index 4aa98a1..a6688d8 100644 --- a/docs/use.md +++ b/docs/use.md @@ -113,7 +113,8 @@ Wireframe and X-ray stay on when you open the next model; the cuts and the measu colour; right-click goes back to the file's colour. Not saved. - **Grid numbers**: **Settings → Viewer → Show scale numbers on the grid** writes millimetres along the build plate, as many as fit at the zoom you look at. -- **Settings** is also the gear icon at the right end of the toolbar, next to the theme icon. +- **Settings** is also the gear icon at the right end of the toolbar, next to the theme icon + (unless you hide it: see [Toolbar buttons](#toolbar-buttons)). More, with pictures: [Look inside your models](inspect.md). Built by [@covrig](https://github.com/covrig). @@ -245,6 +246,28 @@ effect itself and shows a plain colour while the window is not active, and switc *Transparency effects* off in Windows Settings or in battery saver. Windows 10 and other systems keep the normal look. +## Toolbar buttons + +**Settings → Toolbar** (also **☰ → Customize toolbar…**, or right-click the toolbar) chooses which +buttons the toolbar shows, for a calmer toolbar if you don't use every feature: + +- **Can be hidden:** Home, Open, Check, View, Measure, Screenshot, Theme and Settings. +- **Always shown:** the **☰** menu, Open in slicer, Export STL (3MF and CAD files), Plate and Fit. +- **The order never changes.** A hidden button only leaves the toolbar; Plate stays the first of the + view buttons and Fit the last. +- **Nothing gets lost.** A hidden button keeps its key (`C`, `V`, `M`, `Ctrl+W`, …), and the **☰** + menu has everything, including Check, View, Measure and the Theme menu. + +Under **Look** you choose how the view buttons (Plate, Check, View, Measure and Fit) are labelled: +**icon, name and key** (*Plate (P)*, the default), **icon and name** (*Plate*), **icon and key** +(*P*) or **icon only** (the tooltip names it). **Join the view buttons into one group** draws them +as one connected bar. + +![Settings → Toolbar: buttons switched off, shorter labels and a joined group, shown live on the toolbar](assets/toolbar-settings.gif) + +Every change shows in the window, and in the picture at the top of the page, right away: **OK** +keeps it, **Cancel** goes back. **Reset to defaults** shows every button again. + ## Updates **Settings → Updates** shows your version and has **Check for updates now**. You can also switch on diff --git a/docs/use.nl.md b/docs/use.nl.md index dbb07e5..0a59003 100644 --- a/docs/use.nl.md +++ b/docs/use.nl.md @@ -113,7 +113,8 @@ Draadmodel en röntgen blijven aan als je het volgende model opent; de sneden en kleur te geven; rechtsklik zet de kleur uit het bestand terug. Niet opgeslagen. - **Getallen op het raster**: **Instellingen → Weergave → Schaalgetallen op het raster tonen** schrijft millimeters langs de printplaat, zoveel als passen bij de zoom waarmee je kijkt. -- **Instellingen** is ook het tandwielpictogram rechts in de werkbalk, naast het themapictogram. +- **Instellingen** is ook het tandwielpictogram rechts in de werkbalk, naast het themapictogram + (tenzij je het verbergt: zie [Knoppen op de werkbalk](#toolbar-buttons)). Meer, met afbeeldingen: [In je modellen kijken](inspect.md). Gemaakt door [@covrig](https://github.com/covrig). @@ -247,6 +248,30 @@ zelf, toont een effen kleur als het venster niet actief is, en zet het uit als * staan in Windows Instellingen of in de batterijbesparing. Windows 10 en andere systemen houden het gewone uiterlijk. +## Knoppen op de werkbalk {#toolbar-buttons} + +**Instellingen → Werkbalk** (ook **☰ → Werkbalk aanpassen…**, of klik met rechts op de werkbalk) +kiest welke knoppen de werkbalk toont, voor een rustigere werkbalk als je niet alles gebruikt: + +- **Kunnen verborgen worden:** Start, Openen, Controleren, Weergave, Meten, Schermafbeelding, Thema + en Instellingen. +- **Altijd zichtbaar:** het **☰**-menu, Openen in slicer, STL exporteren (3MF- en CAD-bestanden), + Plaat en Passend. +- **De volgorde verandert nooit.** Een verborgen knop verdwijnt alleen van de werkbalk; Plaat blijft + de eerste van de weergaveknoppen en Passend de laatste. +- **Er raakt niets kwijt.** Een verborgen knop houdt zijn toets (`C`, `V`, `M`, `Ctrl+W`, …), en het + **☰**-menu heeft alles, ook Controleren, Weergave, Meten en het themamenu. + +Onder **Uiterlijk** kies je hoe de weergaveknoppen (Plaat, Controleren, Weergave, Meten en Passend) +eruitzien: **pictogram, naam en toets** (*Plaat (P)*, de standaard), **pictogram en naam** (*Plaat*), +**pictogram en toets** (*P*) of **alleen pictogram** (de tooltip noemt de naam). **Weergaveknoppen +samenvoegen tot één groep** tekent ze als één aaneengesloten balk. + +![Instellingen → Werkbalk: knoppen uit, kortere namen en een samengevoegde groep, meteen te zien op de werkbalk](assets/toolbar-settings.gif) + +Elke wijziging is meteen te zien in het venster en in de afbeelding boven aan de pagina: **OK** houdt +hem, **Annuleren** gaat terug. **Standaardwaarden herstellen** toont alle knoppen weer. + ## Updates {#updates} **Instellingen → Updates** toont je versie en heeft **Nu controleren op updates**. Je kunt ook diff --git a/mkdocs.yml b/mkdocs.yml index e2296e6..55f75f0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,6 +50,10 @@ theme: extra_css: - assets/extra.css +# Light / Dark / Ember versions of the screenshots, shown to match the chosen theme. +hooks: + - tools/mkdocs_theme_images.py + plugins: - search - i18n: diff --git a/pyproject.toml b/pyproject.toml index 0629fe6..9b2c6dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "printview3d" -version = "0.29.1" +version = "0.30.0" description = "Lightweight viewer for STL, 3MF, G-code and FreeCAD files with one-click hand-off to your slicer; FreeCAD files go to the slicer as a model directly" readme = "README.md" license = { text = "MIT" } diff --git a/src/printview3d/__init__.py b/src/printview3d/__init__.py index df08342..cc0ace9 100644 --- a/src/printview3d/__init__.py +++ b/src/printview3d/__init__.py @@ -1,5 +1,5 @@ """PrintView3D - a lightweight viewer for STL, 3MF and G-code files.""" -__version__ = "0.29.1" +__version__ = "0.30.0" APP_NAME = "PrintView3D" APP_ID = "PrintView3D" diff --git a/src/printview3d/settings.py b/src/printview3d/settings.py index a26c78d..89a848f 100644 --- a/src/printview3d/settings.py +++ b/src/printview3d/settings.py @@ -42,6 +42,9 @@ "plate_size": [300, 300], # Plate menu -> Custom size: width and depth in mm "thumbnail_open": True, # side panel: the file's preview picture shown (False: folded away) "show_view_cube": True, # Settings -> Viewer: the view cube in the 3D view's corner + "toolbar_hidden": [], # Settings -> Toolbar: buttons switched off (ui/toolbar_prefs.HIDEABLE) + "toolbar_labels": "full", # Settings -> Toolbar: view buttons as "Plate (P)", "Plate", "P" or icon only + "toolbar_joined": False, # Settings -> Toolbar: Plate ... Fit as one connected group "check_angle": 45, # Check: overhangs lean further than this from vertical (degrees) "check_nozzle": 0.4, # Check: thin walls are thinner than this (mm) "check_show": {}, # Check: which highlights are on (kind -> bool; missing: the default) diff --git a/src/printview3d/slicer.py b/src/printview3d/slicer.py index e2be8f7..277bc00 100644 --- a/src/printview3d/slicer.py +++ b/src/printview3d/slicer.py @@ -372,11 +372,6 @@ def program_file_filter() -> str: return "Applications (*.app);;All files (*)" -# Backwards compatible helper (used by older code/tests) -def find_creality_print(saved_path: str = "") -> Optional[str]: - return find_slicer("creality_print", saved_path) - - # -------------------------------------------------------------------------- # Settings helpers # -------------------------------------------------------------------------- diff --git a/src/printview3d/ui/main_window.py b/src/printview3d/ui/main_window.py index 59d1cd5..c470a8e 100644 --- a/src/printview3d/ui/main_window.py +++ b/src/printview3d/ui/main_window.py @@ -37,7 +37,7 @@ from ..loaders.gcode import FEATURE_INDEX from ..settings import EXPORT_MODES, Settings from .labels import feature_label, meta_label, meta_value, plate_mode_label -from . import keys, theme, update_ui +from . import keys, theme, toolbar_prefs as tp, update_ui from .. import updates as up from .export_stl import ExportStlDialog from .scene import PLATE_MODES, Scene @@ -656,6 +656,7 @@ def __init__(self, settings: Optional[Settings] = None): self._retranslate_timer.timeout.connect(self._on_language_changed) self.retranslate_ui() + self.apply_toolbar_layout() # Settings -> Toolbar self._update_view_menu() # nothing open yet: View is greyed out # "Automatic" theme: follow the system when it switches dark/light. @@ -756,6 +757,13 @@ def _build_toolbar(self): # Buttons with an icon show it beside their text; the view buttons have no icon (text only). tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.addToolBar(tb) + self.toolbar = tb + # Settings -> Toolbar: the toolbar's place of every button that can be hidden (its + # QAction in the toolbar, so hiding it leaves the menus and keys alone). + self.toolbar_slots: dict[str, QAction] = {} + # Right-click on the toolbar: Customize toolbar... (instead of Qt's hide-the-toolbar menu). + tb.setContextMenuPolicy(Qt.CustomContextMenu) + tb.customContextMenuRequested.connect(self._toolbar_context_menu) def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QToolButton: """A square icon tile (no text; the tooltip names it).""" @@ -798,8 +806,13 @@ def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QTo self.main_menu.addMenu(self.recent_menu) self.main_menu.addSeparator() self.main_menu.addAction(self.act_screenshot) - self.main_menu.addSeparator() + # Check, View, Measure and the Theme menu are added here at the end of _build_toolbar, so + # every button stays reachable from the menu when it is hidden on the toolbar. + self._menu_settings_sep = self.main_menu.addSeparator() self.main_menu.addAction(self.act_settings) + # Settings -> Toolbar: which buttons the toolbar shows (also a right-click on the toolbar). + self.act_customize_toolbar = QAction(self, triggered=lambda: self.open_settings("toolbar")) + self.main_menu.addAction(self.act_customize_toolbar) self.main_menu.addAction(self.act_updates) self.main_menu.addAction(self.act_whats_new) self.main_menu.addAction(self.act_about) @@ -809,9 +822,9 @@ def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QTo # Home and Open next to the menu. Recent is in the menu only, and the start page lists the # recent files too (#66). self.home_btn = icon_button(action=self.act_home) - tb.addWidget(self.home_btn) + self.toolbar_slots["home"] = tb.addWidget(self.home_btn) self.open_btn = icon_button(action=self.act_open) - tb.addWidget(self.open_btn) + self.toolbar_slots["open"] = tb.addWidget(self.open_btn) tb.addSeparator() self.slicer_btn = QToolButton() @@ -916,7 +929,7 @@ def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QTo self.check_btn.setObjectName("viewButton") self.check_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.check_btn.setDefaultAction(self.act_check) - tb.addWidget(self.check_btn) + self.toolbar_slots["check"] = tb.addWidget(self.check_btn) self.act_view = QAction(self, checkable=True) self.act_view.toggled.connect(self.set_view_section) self.view_btn = QToolButton() @@ -924,7 +937,7 @@ def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QTo self.view_btn.setIconSize(QSize(22, 22)) self.view_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.view_btn.setDefaultAction(self.act_view) - tb.addWidget(self.view_btn) + self.toolbar_slots["view"] = tb.addWidget(self.view_btn) self.measure_group = QActionGroup(self) self.measure_actions = {} for kind in ("distance", "angle", "circle"): @@ -940,7 +953,7 @@ def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QTo self.measure_btn.setIconSize(QSize(22, 22)) self.measure_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.measure_btn.setDefaultAction(self.act_measure) - tb.addWidget(self.measure_btn) + self.toolbar_slots["measure"] = tb.addWidget(self.measure_btn) tb.addAction(self.act_fit) tb.widgetForAction(self.act_fit).setObjectName("viewButton") # The view buttons with an icon: styled to be as tall as the others (theme: hasIcon). @@ -951,9 +964,10 @@ def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QTo spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) tb.addWidget(spacer) + self.toolbar_spacer = spacer # Settings -> Toolbar's picture leaves this gap out self.screenshot_btn = icon_button(action=self.act_screenshot) - tb.addWidget(self.screenshot_btn) + self.toolbar_slots["screenshot"] = tb.addWidget(self.screenshot_btn) # Theme: palette at the end of the toolbar; its menu has the styles, then Light/Dark/System. self.theme_menu = QMenu(self) @@ -980,39 +994,159 @@ def icon_button(menu: QMenu | None = None, action: QAction | None = None) -> QTo self.act_toggle_dark = QAction(self, triggered=self.toggle_light_dark) self.theme_menu.addAction(self.act_toggle_dark) self.theme_btn = icon_button(menu=self.theme_menu) - tb.addWidget(self.theme_btn) + self.toolbar_slots["theme"] = tb.addWidget(self.theme_btn) # Settings, one click away (also in the menu). self.settings_btn = icon_button(action=self.act_settings) - tb.addWidget(self.settings_btn) + self.toolbar_slots["settings"] = tb.addWidget(self.settings_btn) + + # The view buttons by name (Plate first, Fit last), for their labels and the joined group. + self.toolbar_view_buttons = {"plate": self.plate_btn, "check": self.check_btn, "view": self.view_btn, + "measure": self.measure_btn, "fit": tb.widgetForAction(self.act_fit)} + # The menu gets Check, View and Measure (as switches, with their keys) and the Theme menu. + self.menu_mirrors = {} + for kid, act in (("check", self.act_check), ("view", self.act_view), ("measure", self.act_measure)): + m = self._menu_mirror(act) + self.main_menu.insertAction(self._menu_settings_sep, m) + self.menu_mirrors[kid] = m + self.main_menu.insertMenu(self._menu_settings_sep, self.theme_menu) + + def _menu_mirror(self, act: QAction) -> QAction: + """A menu item that works as the toolbar switch `act` does and follows its state. (The + switch itself can't go in the menu: its text has the key in brackets, and the menu would + show the key twice.)""" + m = QAction(self, checkable=True) + m.triggered.connect(lambda _=False: act.trigger()) + act.toggled.connect(m.setChecked) + act.changed.connect(lambda: m.setEnabled(act.isEnabled())) + m.setChecked(act.isChecked()) + m.setEnabled(act.isEnabled()) + return m + + def _toolbar_context_menu(self, pos): + menu = QMenu(self) + menu.addAction(self.act_customize_toolbar) + menu.exec(self.toolbar.mapToGlobal(pos)) + + def apply_toolbar_layout(self, hide: Optional[set] = None, labels: Optional[str] = None, + join: Optional[bool] = None): + """Settings -> Toolbar: show the chosen buttons, label the view buttons and join them or + not. Without arguments, from the settings; Settings passes its choices to try them live + (OK keeps them, Cancel calls this again without arguments). The order never changes.""" + self._toolbar_hidden = tp.hidden(self.settings) if hide is None else set(hide) & set(tp.HIDEABLE) + self._toolbar_labels = tp.label_style(self.settings) if labels not in tp.LABEL_STYLES else labels + self._toolbar_joined = tp.joined(self.settings) if join is None else bool(join) + for kid, slot in self.toolbar_slots.items(): + slot.setVisible(kid not in self._toolbar_hidden) + self._apply_toolbar_labels() + self._update_toolbar_icons() # icon-only view buttons use the icons without the gap + self._update_join_positions() + + def _icon_only(self) -> bool: + return getattr(self, "_toolbar_labels", "full") == "icon" + + def _apply_toolbar_labels(self): + """The text on Plate ... Fit: 'Plate (P)', 'Plate', 'P', or the icon alone. The actions keep + their full names (menus, tooltips); the toolbar shows the icon text.""" + if not hasattr(self, "toolbar_view_buttons"): + return + style = getattr(self, "_toolbar_labels", "full") + names = {"plate": tr("Plate"), "check": tr("Check"), "view": tr("View"), "measure": tr("Measure"), + "fit": tr("Fit")} + acts = {"plate": self.act_bed, "check": self.act_check, "view": self.act_view, + "measure": self.act_measure, "fit": self.act_fit} + for kid, b in self.toolbar_view_buttons.items(): + a = acts[kid] + a.setIconText(tp.button_label(style, names[kid], a.text(), self._k(kid))) + icon_only = style == "icon" + b.setToolButtonStyle(Qt.ToolButtonIconOnly if icon_only else Qt.ToolButtonTextBesideIcon) + b.setIconSize(QSize(22, 22) if icon_only else TEXT_ICON) + if b.property("iconOnly") != icon_only: + b.setProperty("iconOnly", icon_only) + b.style().unpolish(b) + b.style().polish(b) + for kid, m in getattr(self, "menu_mirrors", {}).items(): + key = self._k(kid) + m.setText(names[kid] + (f"\t{key}" if key else "")) + self.theme_menu.setTitle(tr("Theme")) + self.act_customize_toolbar.setText(tr("Customize toolbar…")) + + def _update_join_positions(self): + """The joined group: every visible view button (Iso, Top, Front and Right too while the cube + is off) gets its place in the group, so only the ends are rounded.""" + if not hasattr(self, "toolbar_view_buttons"): + return + tb = self.toolbar + join = getattr(self, "_toolbar_joined", False) + group = [a for a, _, v in self.view_actions if v is not None] + [self.act_bed] + buttons = [tb.widgetForAction(a) for a in group] + buttons += [self.check_btn, self.view_btn, self.measure_btn, tb.widgetForAction(self.act_fit)] + slots = {self.check_btn: self.toolbar_slots.get("check"), self.view_btn: self.toolbar_slots.get("view"), + self.measure_btn: self.toolbar_slots.get("measure")} + def shown(b): + slot = slots.get(b) + if slot is not None: + return slot.isVisible() + act = next((a for a in group + [self.act_fit] if tb.widgetForAction(a) is b), None) + return act is None or act.isVisible() + visible = [b for b in buttons if shown(b)] + pos = tp.join_positions(list(range(len(visible)))) if join else {} + changed = tb.property("joinedGroup") != join + if changed: + tb.setProperty("joinedGroup", join) + for b in buttons: + where = pos.get(visible.index(b), "") if (join and b in visible) else "" + if b.property("joinPos") != where or changed: + b.setProperty("joinPos", where) + b.style().unpolish(b) + b.style().polish(b) + if changed: + tb.style().unpolish(tb) + tb.style().polish(tb) + for b in tb.findChildren(QToolButton): + b.style().unpolish(b) + b.style().polish(b) + tb.updateGeometry() def _update_toolbar_icons(self): - """Draw the toolbar icons in the current theme's colours (again after a theme switch).""" + """Draw the toolbar icons in the current theme's colours (again after a theme switch). + The toolbar buttons get a hover picture too (hover=True): under the mouse the tile fills + with the theme's primary colour, like the view cube. The slicer button's icon is set in + _update_slicer_button: its tile turns white on hover, as the button itself is that colour.""" if not hasattr(self, "theme_btn"): return - self.menu_btn.setIcon(theme.tool_icon("menu")) - self.act_home.setIcon(theme.tool_icon("home")) - self.act_open.setIcon(theme.tool_icon("open")) - # Screenshot and theme are drawn in the primary colour (orange tiles in the Ember themes). - self.act_screenshot.setIcon(theme.tool_icon("screenshot", warm=True)) - self.act_export_stl.setIcon(theme.tool_icon("export", on_accent=theme.filled_view_buttons(), gap=True)) - self.theme_btn.setIcon(theme.tool_icon("theme", warm=True)) + self.menu_btn.setIcon(theme.tool_icon("menu", hover=True)) + self.act_home.setIcon(theme.tool_icon("home", hover=True)) + self.act_open.setIcon(theme.tool_icon("open", hover=True)) + # Screenshot, theme and Settings are drawn in the primary colour (orange tiles in the Ember + # themes), so the three on the right match. + self.act_screenshot.setIcon(theme.tool_icon("screenshot", warm=True, hover=True)) + self.act_export_stl.setIcon(theme.tool_icon("export", on_accent=theme.filled_view_buttons(), gap=True, hover=True)) + self.theme_btn.setIcon(theme.tool_icon("theme", warm=True, hover=True)) self.recent_menu.setIcon(theme.tool_icon("recent")) - self.act_settings.setIcon(theme.tool_icon("settings")) + self.act_settings.setIcon(theme.tool_icon("settings", warm=True, hover=True)) self.act_updates.setIcon(theme.tool_icon("updates")) self.act_about.setIcon(theme.tool_icon("about")) self.act_whats_new.setIcon(theme.tool_icon("whats_new")) # Plate, View and Measure; the buttons of the View and Measure sections. filled = theme.filled_view_buttons() - self.act_bed.setIcon(theme.tool_icon("plate", on_accent=filled, gap=True)) - self.act_view.setIcon(theme.tool_icon("view", on_accent=filled, gap=True)) - self.act_check.setIcon(theme.tool_icon("check", on_accent=filled, gap=True)) - self.act_fit.setIcon(theme.tool_icon("fit", on_accent=filled, gap=True)) - self.act_measure.setIcon(theme.tool_icon("measure", on_accent=filled, gap=True)) + gap = not self._icon_only() # icons beside text keep their gap (theme.ICON_GAP) + self.act_bed.setIcon(theme.tool_icon("plate", on_accent=filled, gap=gap, hover=True)) + self.act_view.setIcon(theme.tool_icon("view", on_accent=filled, gap=gap, hover=True)) + self.act_check.setIcon(theme.tool_icon("check", on_accent=filled, gap=gap, hover=True)) + self.act_fit.setIcon(theme.tool_icon("fit", on_accent=filled, gap=gap, hover=True)) + self.act_measure.setIcon(theme.tool_icon("measure", on_accent=filled, gap=gap, hover=True)) for kind, a in self.measure_actions.items(): a.setIcon(theme.tool_icon(kind, gap=True)) for a, kind in ((self.act_wireframe, "wireframe"), (self.act_xray, "xray"), (self.act_clip, "clip_top"), (self.act_clip_face, "clip_face"), (self.act_reset_view, "reset")): a.setIcon(theme.tool_icon(kind, gap=True)) + for kid, m in getattr(self, "menu_mirrors", {}).items(): + m.setIcon(theme.tool_icon(kid)) + self.theme_menu.setIcon(theme.tool_icon("theme", warm=True)) + self.act_customize_toolbar.setIcon(theme.tool_icon("toolbar")) + # The slicer button's hover picture is in the theme's colour too. + if hasattr(self, "slicer_btn") and hasattr(self, "current_kind") and hasattr(self, "showing_embedded"): + self._update_slicer_button() def _build_preview_page(self) -> QWidget: page = QWidget() @@ -1824,6 +1958,7 @@ def retranslate_ui(self): self._update_slicer_button() self._update_layer_label() self._update_side_min_width() + self._apply_toolbar_labels() # the view buttons' labels name the keys in this language def changeEvent(self, e): if e.type() in (QEvent.LanguageChange, QEvent.LayoutDirectionChange): @@ -2972,6 +3107,7 @@ def _update_view_buttons(self): for act, _, view in self.view_actions: if view in self.CUBE_VIEWS: act.setVisible(not cube_on) + self._update_join_positions() # ============================================================ actions def set_view(self, view: Optional[str], animate: bool = False): @@ -3600,7 +3736,8 @@ def _export_tooltip(self): def _update_slicer_button(self): freecad = self.current_kind == "freecad" and not self.showing_embedded # A 3D printer for slicers; the FreeCAD cube when the button opens FreeCAD. - self.slicer_btn.setIcon(theme.tool_icon("freecad" if freecad else "slicer", on_accent=True, gap=True)) + self.slicer_btn.setIcon(theme.tool_icon("freecad" if freecad else "slicer", on_accent=True, gap=True, + hover=True, on_primary=True)) if freecad: self.slicer_btn.setText(tr("Open in FreeCAD")) self.slicer_btn.setToolTip(tr("Open this file in FreeCAD") + ". " @@ -3915,6 +4052,7 @@ def _settings_closed(self, dlg, result: int): def _settings_applied(self): """Settings saved (OK or Apply): show what changed.""" self._update_slicer_button() + self.apply_toolbar_layout() # FreeCAD just chosen: show the open FreeCAD file in 3D now. freecad_before = getattr(self, "_freecad_before", self.settings.get("freecad_path", "")) self._freecad_before = self.settings.get("freecad_path", "") diff --git a/src/printview3d/ui/print_check.py b/src/printview3d/ui/print_check.py index d0f4403..e2ba159 100644 --- a/src/printview3d/ui/print_check.py +++ b/src/printview3d/ui/print_check.py @@ -316,11 +316,6 @@ class _Signals(QObject): BACKGROUND_FACES = 200_000 # a mesh check on more faces than this runs in the background -def _hex_rgb(h: str) -> tuple: - h = h.lstrip("#") - return tuple(int(h[i:i + 2], 16) / 255 for i in (0, 2, 4)) - - def _polygon_offset_only_where_asked(): """Lets a highlight be drawn just in front of the faces it lies on (a per-actor offset). diff --git a/src/printview3d/ui/scene.py b/src/printview3d/ui/scene.py index 787f322..60a5617 100644 --- a/src/printview3d/ui/scene.py +++ b/src/printview3d/ui/scene.py @@ -311,11 +311,6 @@ def apply_plate(self): self._draw_plate_axes() # --------------------------------------------------------------- X/Y/Z on the plate - def set_plate_axes(self, on: bool): - """Settings -> Viewer: arrows for X, Y and Z on the plate, next to the model.""" - self.show_plate_axes = bool(on) - self._draw_plate_axes() - def plate_axes_frame(self) -> Optional[tuple]: """(origin, length) of the arrows: at the model's back-right corner, a small gap outside its footprint so they never touch it, pointing away from it. The usual view looks from the diff --git a/src/printview3d/ui/settings_dialog.py b/src/printview3d/ui/settings_dialog.py index 0827a82..9e33e4d 100644 --- a/src/printview3d/ui/settings_dialog.py +++ b/src/printview3d/ui/settings_dialog.py @@ -9,12 +9,12 @@ import os import sys -from PySide6.QtCore import QDateTime, QLocale, QSize, Qt, Signal -from PySide6.QtGui import QIcon, QKeySequence +from PySide6.QtCore import QDateTime, QLocale, QSize, Qt, QTimer, Signal +from PySide6.QtGui import QIcon, QKeySequence, QPainter, QPixmap from PySide6.QtWidgets import ( QApplication, QButtonGroup, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem, QMessageBox, - QKeySequenceEdit, QPushButton, QScrollArea, QSizePolicy, QSlider, QSpinBox, QStackedWidget, QToolButton, QVBoxLayout, + QKeySequenceEdit, QPushButton, QScrollArea, QSizePolicy, QSlider, QStackedWidget, QToolButton, QVBoxLayout, QWidget, ) @@ -23,7 +23,7 @@ from .. import freecad as fc from .. import slicer as sl from ..i18n import tr -from . import keys, theme +from . import keys, theme, toolbar_prefs as tp from . import update_ui from .labels import assoc_status, plate_mode_label @@ -182,6 +182,9 @@ def __init__(self, settings, parent=None, gpu_info=None, page: str = ""): la.addWidget(mica_note) self._add_page("appearance", tr("Appearance"), [gt, ga], keep_titles=True) + # ---------------- Toolbar ---------------- + self._add_page("toolbar", tr("Toolbar"), self._build_toolbar_page(settings), keep_titles=True) + # ---------------- Slicer ---------------- self._paths = dict(settings.get("slicer_paths") or {}) self._custom = dict(settings.get("custom_slicer") or {}) @@ -626,6 +629,124 @@ def show_page(self, key: str): if key in self._page_keys: self.nav.setCurrentRow(self._page_keys.index(key)) + # -------------------------------------------------------------- toolbar + def _build_toolbar_page(self, settings) -> list: + """Which toolbar buttons are shown, and how the view buttons look. Every change shows in the + window right away (and in the picture on top); OK keeps it, Cancel goes back.""" + self._tb_at_open = (tp.hidden(settings), tp.label_style(settings), tp.joined(settings)) + intro = QLabel(tr("Choose which buttons the toolbar shows. A hidden button keeps its key, and " + "everything stays in the ☰ menu. The order never changes.")) + intro.setObjectName("muted") + intro.setWordWrap(True) + self.toolbar_preview = QLabel() + self.toolbar_preview.setObjectName("toolbarPreview") + self.toolbar_preview.setFrameShape(QFrame.StyledPanel) + self.toolbar_preview.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + top = QWidget() + tl = QVBoxLayout(top) + tl.setContentsMargins(0, 0, 0, 0) + tl.addWidget(intro) + tl.addWidget(self.toolbar_preview) + + gb = QGroupBox(tr("Buttons, in toolbar order")) + grid = QGridLayout(gb) + names = {"menu": tr("Menu"), "home": tr("Home"), "open": tr("Open"), "slicer": tr("Open in slicer"), + "export": tr("Export STL"), "plate": tr("Plate"), "check": tr("Check"), "view": tr("View"), + "measure": tr("Measure"), "fit": tr("Fit"), "screenshot": tr("Screenshot"), + "theme": tr("Theme"), "settings": tr("Settings")} + self.toolbar_checks: dict[str, QCheckBox] = {} + hidden = self._tb_at_open[0] + rows = (len(tp.BUTTONS) + 1) // 2 + for i, (kid, can_hide) in enumerate(tp.BUTTONS): + if can_hide: + cb = QCheckBox(names[kid]) + cb.setChecked(kid not in hidden) + cb.toggled.connect(self._preview_toolbar) + self.toolbar_checks[kid] = cb + else: + cb = QCheckBox(tr("{name} · always shown").format(name=names[kid])) + cb.setChecked(True) + cb.setEnabled(False) + grid.addWidget(cb, i % rows, i // rows) + + gl = QGroupBox(tr("Look")) + fl = QFormLayout(gl) + self.toolbar_labels_combo = QComboBox() + for text, key in ((tr("Icon, name and key"), "full"), (tr("Icon and name"), "name"), + (tr("Icon and key"), "key"), (tr("Icon only"), "icon")): + self.toolbar_labels_combo.addItem(text, key) + self.toolbar_labels_combo.setCurrentIndex(tp.LABEL_STYLES.index(self._tb_at_open[1])) + self.toolbar_labels_combo.currentIndexChanged.connect(self._preview_toolbar) + fl.addRow(tr("View buttons show:"), self.toolbar_labels_combo) + self.toolbar_joined_cb = QCheckBox(tr("Join the view buttons into one group")) + self.toolbar_joined_cb.setChecked(self._tb_at_open[2]) + self.toolbar_joined_cb.toggled.connect(self._preview_toolbar) + fl.addRow(self.toolbar_joined_cb) + reset = QPushButton(tr("Reset to defaults")) + reset.clicked.connect(self._reset_toolbar) + reset_row = QHBoxLayout() + reset_row.addWidget(reset) + reset_row.addStretch(1) + fl.addRow(reset_row) + QTimer.singleShot(0, self._draw_toolbar_preview) + return [top, gb, gl] + + def _toolbar_choice(self) -> tuple[set, str, bool]: + hide = {kid for kid, cb in self.toolbar_checks.items() if not cb.isChecked()} + return hide, self.toolbar_labels_combo.currentData(), self.toolbar_joined_cb.isChecked() + + def _preview_toolbar(self, *_): + """Show the choice in the window now (OK keeps it, Cancel goes back).""" + parent = self.parent() + if hasattr(parent, "apply_toolbar_layout"): + parent.apply_toolbar_layout(*self._toolbar_choice()) + QTimer.singleShot(0, self._draw_toolbar_preview) + + def _draw_toolbar_preview(self): + """A picture of the window's toolbar as it is now, as wide as the page allows.""" + bar = getattr(self.parent(), "toolbar", None) + if bar is None: + self.toolbar_preview.hide() + return + pm = bar.grab() + gap = getattr(self.parent(), "toolbar_spacer", None) + if gap is not None and gap.width() > 40: + # The empty stretch in the middle left out (a short gap instead), so the buttons show + # larger in the picture. + g = gap.geometry() + ratio = pm.devicePixelRatio() + keep = 24 + left = pm.copy(0, 0, round(g.x() * ratio), pm.height()) + right = pm.copy(round((g.right() + 1) * ratio), 0, pm.width() - round((g.right() + 1) * ratio), + pm.height()) + joined = QPixmap(left.width() + round(keep * ratio) + right.width(), pm.height()) + joined.fill(Qt.transparent) + p = QPainter(joined) + p.drawPixmap(0, 0, left) + p.drawPixmap(left.width(), 0, pm.copy(round(g.x() * ratio), 0, round(keep * ratio), pm.height())) + p.drawPixmap(left.width() + round(keep * ratio), 0, right) + p.end() + joined.setDevicePixelRatio(ratio) + pm = joined + width = max(320, self.pages.width() - 40) + if pm.deviceIndependentSize().width() > width: + pm = pm.scaledToWidth(round(width * pm.devicePixelRatio()), Qt.SmoothTransformation) + self.toolbar_preview.setPixmap(pm) + + def _reset_toolbar(self): + """Every button shown, labels with name and key, not joined.""" + for cb in self.toolbar_checks.values(): + cb.blockSignals(True) + cb.setChecked(True) + cb.blockSignals(False) + self.toolbar_labels_combo.blockSignals(True) + self.toolbar_labels_combo.setCurrentIndex(0) + self.toolbar_labels_combo.blockSignals(False) + self.toolbar_joined_cb.blockSignals(True) + self.toolbar_joined_cb.setChecked(False) + self.toolbar_joined_cb.blockSignals(False) + self._preview_toolbar() + # -------------------------------------------------------------- updates def _build_updates(self) -> QGroupBox: g = QGroupBox() @@ -855,6 +976,8 @@ def reject(self): self._preview_plane_strength(self._plane_at_open) if self.line_slider.value() != self._line_at_open: self._preview_line_width(self._line_at_open) + if self._toolbar_choice() != self._tb_at_open and hasattr(self.parent(), "apply_toolbar_layout"): + self.parent().apply_toolbar_layout(*self._tb_at_open) super().reject() @staticmethod @@ -1194,6 +1317,7 @@ def _save(self, close: bool = True): self.settings.set("plate_mode", self.plate_combo.currentData()) self.settings.set("plate_size", [round(self.plate_w_spin.mm(), 3), round(self.plate_d_spin.mm(), 3)]) self.settings.set("show_view_cube", self.cube_cb.isChecked()) + tp.save(self.settings, *self._toolbar_choice()) self.settings.set("ghost_strength", self.ghost_slider.value()) self.settings.set("plane_strength", self.plane_slider.value()) self.settings.set("measure_line_width", self.line_slider.value()) @@ -1238,6 +1362,7 @@ def _save(self, close: bool = True): self._ghost_at_open = self.ghost_slider.value() self._plane_at_open = self.plane_slider.value() self._line_at_open = self.line_slider.value() + self._tb_at_open = self._toolbar_choice() self.applied.emit() def _ask_restart(self, what: list[str]) -> bool: diff --git a/src/printview3d/ui/theme.py b/src/printview3d/ui/theme.py index 5c760e2..7e0a6a1 100644 --- a/src/printview3d/ui/theme.py +++ b/src/printview3d/ui/theme.py @@ -429,7 +429,7 @@ def apply_theme(app, choice: str = "auto", mica: bool = False) -> str: for role in (QPalette.WindowText, QPalette.Text, QPalette.ButtonText): pal.setColor(QPalette.Disabled, role, c(t["disabled"])) app.setPalette(pal) - accent, accent_hover = t["accent"], t["accent_hover"] + accent = t["accent"] # An option button that is on (View and Measure sections): the accent, lightly, as its background. _r, _g, _b = (int(accent.lstrip("#")[i:i + 2], 16) for i in (0, 2, 4)) toggle_on = f"rgba({_r}, {_g}, {_b}, {0.32 if is_dark() else 0.26})" @@ -465,6 +465,22 @@ def apply_theme(app, choice: str = "auto", mica: bool = False) -> str: QToolBar QToolButton#viewButton[popupMode="1"] {{ padding-right: 24px; }} QToolBar QToolButton#viewButton::menu-button {{ width: 18px; border-left: 1px solid {divider}; border-top-right-radius: 5px; border-bottom-right-radius: 5px; }} + /* Settings -> Toolbar. Icon only: the view buttons sit evenly around their icon. */ + QToolBar QToolButton#viewButton[iconOnly="true"] {{ padding: 2px 3px; }} + QToolBar QToolButton#viewButton[iconOnly="true"][popupMode="1"] {{ padding-right: 21px; }} + /* Joined: the view buttons touch, with a thin line between them; only the group's ends are + round. The other buttons keep the usual gap (no toolbar spacing, a wider margin instead). */ + QToolBar[joinedGroup="true"] {{ spacing: 0px; }} + QToolBar[joinedGroup="true"] QToolButton {{ margin: 1px 4px; }} + QToolBar[joinedGroup="true"] QToolButton#viewButton[joinPos="first"] {{ margin: 1px 0px 1px 4px; + border-top-right-radius: 0px; border-bottom-right-radius: 0px; }} + QToolBar[joinedGroup="true"] QToolButton#viewButton[joinPos="middle"] {{ margin: 1px 0px; + border-radius: 0px; border-left-color: {divider}; }} + QToolBar[joinedGroup="true"] QToolButton#viewButton[joinPos="last"] {{ margin: 1px 4px 1px 0px; + border-top-left-radius: 0px; border-bottom-left-radius: 0px; border-left-color: {divider}; }} + QToolBar[joinedGroup="true"] QToolButton#viewButton[joinPos="first"]::menu-button, + QToolBar[joinedGroup="true"] QToolButton#viewButton[joinPos="middle"]::menu-button {{ + border-top-right-radius: 0px; border-bottom-right-radius: 0px; }} QToolButton#slicerButton {{ background: {primary}; color: white; font-weight: 600; padding: 2px 14px 2px 3px; }} QToolButton#slicerButton:hover {{ background: {primary_hover}; }} QToolButton#slicerButton[popupMode="1"] {{ padding-right: 28px; }} @@ -668,25 +684,6 @@ def toggle_light_dark(choice: str) -> str: return make_choice(style, "light" if is_dark(resolve(choice)) else "dark") -def theme_icon(name: str | None = None) -> QIcon: - """Half-filled circle (the usual 'theme' symbol) in the theme's text colour.""" - size = 64 # drawn large, Qt scales it down sharply - pm = QPixmap(size, size) - pm.fill(Qt.transparent) - p = QPainter(pm) - p.setRenderHint(QPainter.Antialiasing) - fg = QColor(colors(name)["text"]) - p.setPen(QPen(fg, 5)) - c, r = QPointF(size / 2, size / 2), 24 - p.drawEllipse(c, r, r) - half = QPainterPath() - half.moveTo(c) - half.arcTo(QRectF(c.x() - r, c.y() - r, 2 * r, 2 * r), 90, 180) - p.fillPath(half, fg) - p.end() - return QIcon(pm) - - def theme_preview(name: str, width: int = 168, height: int = 104, ratio: float = 2.0) -> QPixmap: """A small picture of PrintView3D in a theme: toolbar with its tiles and slicer button, the side panel with a chosen row, and the 3D view with plate and model. Used by the theme @@ -876,6 +873,12 @@ def _nav_pixmap(kind: str, dot: bool, color: str, ring: str) -> QPixmap: for a, b in ((top, l_up), (top, r_up), (l_up, mid), (r_up, mid), (mid, bot), (l_up, l_dn), (r_up, r_dn), (l_dn, bot), (r_dn, bot)): p.drawLine(a, b) + elif kind == "toolbar": # a bar with three buttons on it + p.drawRoundedRect(QRectF(6, 18, 52, 28), 6, 6) + p.setBrush(fg) + for x in (14, 28, 42): + p.drawRoundedRect(QRectF(x - 1, 27, 10, 10), 2, 2) + p.setBrush(Qt.NoBrush) elif kind == "files": # folder path = QPainterPath() path.moveTo(8, 18) @@ -930,6 +933,12 @@ def _tool_glyph(p: QPainter, kind: str, fg: QColor) -> None: if kind == "menu": # hamburger for y in (18, 32, 46): p.drawLine(QPointF(12, y), QPointF(52, y)) + elif kind == "toolbar": # a bar with three buttons on it + p.drawRoundedRect(QRectF(6, 18, 52, 28), 6, 6) + p.setBrush(fg) + for x in (14, 28, 42): + p.drawRoundedRect(QRectF(x - 1, 27, 10, 10), 2, 2) + p.setBrush(Qt.NoBrush) elif kind == "home": # house: roof, walls and a door house = QPainterPath() house.moveTo(8, 30) @@ -1151,7 +1160,7 @@ def _tool_glyph(p: QPainter, kind: str, fg: QColor) -> None: p.drawLine(QPointF(58, 40), QPointF(50, 48)) -TOOL_ICONS = ("menu", "home", "open", "recent", "screenshot", "theme", "slicer", "freecad", "export", +TOOL_ICONS = ("menu", "toolbar", "home", "open", "recent", "screenshot", "theme", "slicer", "freecad", "export", "settings", "updates", "about", "whats_new", "view", "plate", "measure", "fit", "check", "distance", "angle", "circle", "wireframe", "xray", "clip_top", "clip_face", "reset") @@ -1160,19 +1169,22 @@ def _tool_glyph(p: QPainter, kind: str, fg: QColor) -> None: ICON_GAP = 6 # px between an icon and the text beside it (at 22 px icons) -def tool_icon(kind: str, on_accent: bool = False, warm: bool = False, gap: bool = False) -> QIcon: +def tool_icon(kind: str, on_accent: bool = False, warm: bool = False, gap: bool = False, + hover: bool = False, on_primary: bool = False) -> QIcon: """Toolbar icon: the symbol in the accent colour on a soft accent-tinted tile. on_accent=True is for the slicer button: a white symbol on a soft white tile. warm=True uses the theme's primary colour instead (the orange tiles of the Ember themes; the classic themes have the same teal for both, so there it looks the same). gap=True is for buttons with text beside the icon: the picture is wider by ICON_GAP (empty on the right), so the text keeps some room from the icon. Its buttons use an icon size of - (22 + ICON_GAP) x 22.""" - size = 64 - pm = QPixmap(size + (round(size * ICON_GAP / 22) if gap else 0), size) - pm.fill(Qt.transparent) - p = QPainter(pm) - p.setRenderHint(QPainter.Antialiasing) + (22 + ICON_GAP) x 22. + hover=True adds a second picture that Qt shows while the mouse is over the button (QIcon's + Active mode): the tile filled solid in the theme's primary colour with a white or dark symbol, + whichever reads better - the same rule as the view cube's hovered part (amber in Ember, teal + in the other themes). Only for toolbar buttons: a focused push button would show it too. + on_primary=True (with on_accent) is for the slicer button, which is itself filled with the + primary colour: there the hovered tile turns solid white with the symbol in a deeper shade of + that colour, so it stands out instead of vanishing into the button.""" t = colors() if on_accent: fg, tile = QColor("#ffffff"), QColor(255, 255, 255, 46) @@ -1181,6 +1193,25 @@ def tool_icon(kind: str, on_accent: bool = False, warm: bool = False, gap: bool fg = QColor(t["primary_ink" if warm else "accent_ink"]) tile = QColor(t["primary" if warm else "accent"]) tile.setAlphaF(0.22 if not is_dark() else 0.20) + icon = QIcon(_tool_pixmap(kind, fg, tile, gap)) + if hover: + from .view_cube import readable_text # the cube's white-or-dark text rule + if on_primary: + fg_hover, fill = QColor(t["primary"]).darker(125), QColor("#ffffff") + else: + fill = QColor(t["primary"]) + fg_hover = QColor(readable_text(t["primary"])) + icon.addPixmap(_tool_pixmap(kind, fg_hover, fill, gap), QIcon.Active) + return icon + + +def _tool_pixmap(kind: str, fg: QColor, tile: QColor, gap: bool) -> QPixmap: + """One picture of a toolbar icon: the symbol in fg on a rounded tile.""" + size = 64 + pm = QPixmap(size + (round(size * ICON_GAP / 22) if gap else 0), size) + pm.fill(Qt.transparent) + p = QPainter(pm) + p.setRenderHint(QPainter.Antialiasing) p.setPen(Qt.NoPen) p.setBrush(tile) p.drawRoundedRect(QRectF(0, 0, size, size), 14, 14) @@ -1194,9 +1225,4 @@ def tool_icon(kind: str, on_accent: bool = False, warm: bool = False, gap: bool p.setBrush(Qt.NoBrush) _tool_glyph(p, kind, fg) p.end() - return QIcon(pm) - - -def apply_dark_theme(app): - """Kept for older scripts.""" - return apply_theme(app, "dark") + return pm diff --git a/src/printview3d/ui/toolbar_prefs.py b/src/printview3d/ui/toolbar_prefs.py new file mode 100644 index 0000000..572ed5d --- /dev/null +++ b/src/printview3d/ui/toolbar_prefs.py @@ -0,0 +1,65 @@ +"""Settings -> Toolbar: which toolbar buttons are shown and how the view buttons look. + +The toolbar's order never changes: hiding a button only takes it out, the rest keep their places +(Plate always first of the view buttons, Fit always last). The menu, Open in slicer, Export STL, +Plate and Fit are always shown; everything stays in the menu and keeps its key while hidden. +""" + +from __future__ import annotations + +# Every toolbar button, left to right: (id, can be hidden). +BUTTONS: tuple[tuple[str, bool], ...] = ( + ("menu", False), ("home", True), ("open", True), ("slicer", False), ("export", False), + ("plate", False), ("check", True), ("view", True), ("measure", True), ("fit", False), + ("screenshot", True), ("theme", True), ("settings", True), +) +HIDEABLE: tuple[str, ...] = tuple(b for b, can in BUTTONS if can) + +# The view buttons with an icon (Plate ... Fit), as they are labelled. +VIEW_BUTTONS: tuple[str, ...] = ("plate", "check", "view", "measure", "fit") +# How those buttons are labelled: "Plate (P)", "Plate", "P" or the icon alone. +LABEL_STYLES: tuple[str, ...] = ("full", "name", "key", "icon") + + +def hidden(settings) -> set[str]: + """The buttons the user switched off (unknown names and always-shown buttons ignored).""" + value = settings.get("toolbar_hidden", []) or [] + if not isinstance(value, (list, tuple)): + return set() + return {b for b in value if b in HIDEABLE} + + +def label_style(settings) -> str: + value = settings.get("toolbar_labels", "full") + return value if value in LABEL_STYLES else "full" + + +def joined(settings) -> bool: + return bool(settings.get("toolbar_joined", False)) + + +def save(settings, hide: set[str], labels: str, join: bool) -> None: + """Store the choice; hidden buttons in toolbar order, so the file reads naturally.""" + settings.set("toolbar_hidden", [b for b in HIDEABLE if b in hide]) + settings.set("toolbar_labels", labels if labels in LABEL_STYLES else "full") + settings.set("toolbar_joined", bool(join)) + + +def button_label(style: str, name: str, with_key: str, key: str) -> str: + """The text on a view button: with_key is 'Plate (P)', key is 'P' ('' when it has none).""" + if style == "name" or style == "icon": + return name + if style == "key": + return key or name # a button without a key keeps its name + return with_key + + +def join_positions(visible: list[str]) -> dict[str, str]: + """Where each visible button sits in the joined group: 'first', 'middle', 'last' or 'only'.""" + if len(visible) == 1: + return {visible[0]: "only"} + out = {b: "middle" for b in visible} + if visible: + out[visible[0]] = "first" + out[visible[-1]] = "last" + return out diff --git a/src/printview3d/units.py b/src/printview3d/units.py index 58877f8..56adb88 100644 --- a/src/printview3d/units.py +++ b/src/printview3d/units.py @@ -40,10 +40,6 @@ def to_display(mm: float) -> float: return mm / MM_PER_IN if imperial() else mm -def from_display(value: float) -> float: - return value * MM_PER_IN if imperial() else value - - def decimals(mm_decimals: int) -> int: """Decimals for a length shown with `mm_decimals` in millimetres: an inch is 25.4 mm, so inches need two more (at most three) to say as much: 0.1 mm ~ 0.004 in.""" diff --git a/src/printview3d/updates.py b/src/printview3d/updates.py index 696296a..e28200a 100644 --- a/src/printview3d/updates.py +++ b/src/printview3d/updates.py @@ -135,13 +135,6 @@ def _absolute_links(md: str) -> str: lambda m: f"](https://github.com/{REPO}/blob/main/{m.group(1)})", md) -def notes_since(older: str, current: str = __version__, text: str | None = None) -> list[tuple[str, str]]: - """Changelog sections newer than `older` and not newer than `current`, newest first.""" - text = changelog_text() if text is None else text - return [(v, _absolute_links(n)) for v, n in changelog_sections(text) - if is_newer(v, older) and not is_newer(v, current)] - - @dataclass class Notes: """One version's notes as shown: in the user's language, or in English when that version diff --git a/tests/test_theme.py b/tests/test_theme.py index faf56eb..ce9a6c1 100644 --- a/tests/test_theme.py +++ b/tests/test_theme.py @@ -121,11 +121,6 @@ def test_toggle_light_dark(monkeypatch): assert theme.toggle_light_dark("auto_ember") == "ember" # from System: the opposite of now -def test_theme_icon_draws(app): - for name in theme.THEMES: - assert not theme.theme_icon(name).isNull() - - def test_segment_icons_draw(app): for kind in ("light", "dark", "system", "off", "mica", "acrylic"): assert not theme.segment_icon(kind).isNull() @@ -237,6 +232,32 @@ def test_toolbar_icons_draw_in_every_theme(app): theme.apply_theme(app, "dark") +def test_toolbar_icons_fill_with_the_primary_colour_on_hover(app): + """Under the mouse a toolbar icon's tile fills with the theme's primary colour (amber in + Ember, teal elsewhere), like the view cube's hovered part; without hover=True it has no + hover picture, so push buttons with a focus never show it.""" + from PySide6.QtCore import QSize + from PySide6.QtGui import QColor, QIcon + from printview3d.ui.view_cube import readable_text + for name in theme.THEMES: + theme.apply_theme(app, name) + t = theme.colors() + for kwargs in ({}, {"warm": True}, {"on_accent": True, "gap": True}): + icon = theme.tool_icon("open", hover=True, **kwargs) + normal = icon.pixmap(QSize(64, 64), QIcon.Normal).toImage() + hover = icon.pixmap(QSize(64, 64), QIcon.Active).toImage() + assert QColor(hover.pixel(3, 32)).name() == t["primary"], (name, kwargs) + assert QColor(normal.pixel(3, 32)).name() != t["primary"] or normal.pixelColor(3, 32).alpha() < 255 + assert readable_text(t["primary"]) in ("#ffffff", "#1a1d21") + slicer = theme.tool_icon("slicer", on_accent=True, gap=True, hover=True, on_primary=True) + assert QColor(slicer.pixmap(QSize(80, 64), QIcon.Active).toImage().pixel(3, 32)).name() == "#ffffff", name + plain = theme.tool_icon("open") + a = plain.pixmap(QSize(64, 64), QIcon.Normal).toImage() + b = plain.pixmap(QSize(64, 64), QIcon.Active).toImage() + assert a == b, name + theme.apply_theme(app, "dark") + + def test_keys_page_lists_the_shortcuts(app, tmp_path): """The Settings window builds, and Keyboard and mouse shows every key of the main window.""" from printview3d.ui import keys as k diff --git a/tests/test_toolbar_layout.py b/tests/test_toolbar_layout.py new file mode 100644 index 0000000..de03514 --- /dev/null +++ b/tests/test_toolbar_layout.py @@ -0,0 +1,194 @@ +"""Settings -> Toolbar: hiding toolbar buttons (the order never changes), how the view buttons are +labelled, the joined group, the menu entries that keep everything reachable, and the Settings page +(live preview, OK keeps, Cancel goes back).""" + +import os + +import pytest + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + +from printview3d import units # noqa: E402 +from printview3d.settings import Settings # noqa: E402 +from printview3d.ui import toolbar_prefs as tp # noqa: E402 + +HERE = os.path.dirname(os.path.abspath(__file__)) +CUBE = os.path.join(HERE, "data", "cube_binary.stl") + + +@pytest.fixture +def app(): + from PySide6.QtWidgets import QApplication + return QApplication.instance() or QApplication([]) + + +@pytest.fixture +def window(app, tmp_path): + from PySide6.QtCore import QEvent + from printview3d.ui import main_window as mw + w = mw.MainWindow(Settings(str(tmp_path / "settings.json"))) + w.resize(1500, 800) + w.show() + w.load_sync(CUBE) + app.processEvents() + yield w + w.close() + w.deleteLater() + app.sendPostedEvents(None, QEvent.DeferredDelete) + app.processEvents() + units.set_unit("mm") + + +def _shown_order(w): + """The toolbar's visible buttons, left to right, by id.""" + tb = w.toolbar + ids = {w.menu_btn: "menu", w.home_btn: "home", w.open_btn: "open", w.slicer_btn: "slicer", + w.export_btn: "export", w.plate_btn: "plate", w.check_btn: "check", w.view_btn: "view", + w.measure_btn: "measure", tb.widgetForAction(w.act_fit): "fit", w.screenshot_btn: "screenshot", + w.theme_btn: "theme", w.settings_btn: "settings"} + out = [] + for a in tb.actions(): + b = tb.widgetForAction(a) + if b in ids and a.isVisible(): + out.append(ids[b]) + return out + + +# ------------------------------------------------------------------ the settings +def test_prefs_ignore_unknown_and_always_shown_buttons(tmp_path): + s = Settings(str(tmp_path / "s.json")) + s.set("toolbar_hidden", ["plate", "fit", "menu", "slicer", "export", "nonsense", "check"]) + s.set("toolbar_labels", "huge") + assert tp.hidden(s) == {"check"} and tp.label_style(s) == "full" and not tp.joined(s) + tp.save(s, {"settings", "home", "plate"}, "icon", True) + assert s.get("toolbar_hidden") == ["home", "settings"] # toolbar order, Plate dropped + assert tp.label_style(s) == "icon" and tp.joined(s) + + +def test_labels_and_join_positions(): + assert tp.button_label("full", "Plate", "Plate (P)", "P") == "Plate (P)" + assert tp.button_label("name", "Plate", "Plate (P)", "P") == "Plate" + assert tp.button_label("key", "Plate", "Plate (P)", "P") == "P" + assert tp.button_label("key", "Plate", "Plate", "") == "Plate" # no key: the name stays + assert tp.join_positions(["a"]) == {"a": "only"} + assert tp.join_positions(["a", "b", "c"]) == {"a": "first", "b": "middle", "c": "last"} + + +# ------------------------------------------------------------------ the window +def test_hiding_keeps_the_order_plate_first_fit_last(window): + full = _shown_order(window) + assert full == [b for b, _ in tp.BUTTONS if b != "export"] # Export STL: 3MF and CAD files only + window.apply_toolbar_layout({"home", "check", "measure", "theme"}, "full", False) + assert _shown_order(window) == ["menu", "open", "slicer", "plate", "view", "fit", + "screenshot", "settings"] + window.apply_toolbar_layout(set(tp.HIDEABLE), "full", False) + assert _shown_order(window) == ["menu", "slicer", "plate", "fit"] + window.apply_toolbar_layout(set(), "full", False) + assert _shown_order(window) == full + + +def test_hidden_buttons_keep_their_keys_and_menu_entries(window): + window.apply_toolbar_layout({"check", "view", "measure", "settings", "theme"}, "full", False) + texts = [a.text().split("\t")[0] for a in window.main_menu.actions()] + for name in ("Check", "View", "Measure", "Settings", "Theme", "Customize toolbar…"): + assert name in texts, name + assert not window.act_check.shortcut().isEmpty() # C still works + window.act_check.trigger() + assert window.menu_mirrors["check"].isChecked() # the menu follows the switch + window.menu_mirrors["check"].trigger() + assert not window.act_check.isChecked() # and the switch follows the menu + assert window.menu_mirrors["check"].text() == "Check\tC" + + +def test_label_styles(window): + from PySide6.QtCore import Qt + for style, plate, fit in (("full", "Plate (P)", "Fit (F)"), ("name", "Plate", "Fit"), ("key", "P", "F")): + window.apply_toolbar_layout(set(), style, False) + assert window.act_bed.iconText() == plate and window.act_fit.iconText() == fit + assert window.check_btn.toolButtonStyle() == Qt.ToolButtonTextBesideIcon + assert window.act_check.text() == "Check (C)" # the full name stays for menus + window.apply_toolbar_layout(set(), "icon", False) + for b in window.toolbar_view_buttons.values(): + assert b.toolButtonStyle() == Qt.ToolButtonIconOnly and b.property("iconOnly") is True + assert b.iconSize().width() == 22 + # icon only: the icons without the gap for text (square) + pm = window.act_check.icon().availableSizes() + assert not pm or pm[0].width() == pm[0].height() + + +def test_joined_group_follows_what_is_shown(window): + tb = window.toolbar + window.apply_toolbar_layout(set(), "name", True) + assert tb.property("joinedGroup") is True + pos = {k: b.property("joinPos") for k, b in window.toolbar_view_buttons.items()} + assert pos == {"plate": "first", "check": "middle", "view": "middle", "measure": "middle", "fit": "last"} + window.apply_toolbar_layout({"check", "view", "measure"}, "name", True) + assert window.plate_btn.property("joinPos") == "first" + assert tb.widgetForAction(window.act_fit).property("joinPos") == "last" + # cube off: Iso ... Right come back and join the group at its start + window.set_view_cube_visible(False) + iso = next(a for a, _, v in window.view_actions if v == "iso") + assert tb.widgetForAction(iso).property("joinPos") == "first" + assert window.plate_btn.property("joinPos") == "middle" + window.set_view_cube_visible(True) + assert window.plate_btn.property("joinPos") == "first" + window.apply_toolbar_layout(set(), "full", False) + assert tb.property("joinedGroup") is False + assert all(not b.property("joinPos") for b in window.toolbar_view_buttons.values()) + + +def test_saved_choice_is_used_at_start(app, tmp_path): + from printview3d.ui import main_window as mw + s = Settings(str(tmp_path / "settings.json")) + tp.save(s, {"home", "measure"}, "name", True) + w = mw.MainWindow(s) + try: + w.show() + app.processEvents() + assert not w.toolbar_slots["home"].isVisible() and not w.toolbar_slots["measure"].isVisible() + assert w.toolbar_slots["check"].isVisible() + assert w.act_bed.iconText() == "Plate" and w.toolbar.property("joinedGroup") is True + finally: + w.close() + + +def test_right_click_offers_customize(window): + from PySide6.QtCore import Qt + assert window.toolbar.contextMenuPolicy() == Qt.CustomContextMenu + assert window.act_customize_toolbar.text() == "Customize toolbar…" + + +# ------------------------------------------------------------------ the Settings page +def test_settings_page_previews_live_ok_keeps_cancel_goes_back(window, app): + from printview3d.ui.settings_dialog import SettingsDialog + d = SettingsDialog(window.settings, window, page="toolbar") + d.show() + app.processEvents() + assert d._page_keys[d.nav.currentRow()] == "toolbar" + always = [cb.text() for cb in d.findChildren(type(d.toolbar_joined_cb)) if not cb.isEnabled()] + assert any(t.startswith("Plate") for t in always) and any(t.startswith("Fit") for t in always) + assert set(d.toolbar_checks) == set(tp.HIDEABLE) + d.toolbar_checks["measure"].setChecked(False) + d.toolbar_labels_combo.setCurrentIndex(tp.LABEL_STYLES.index("key")) + app.processEvents() + assert not window.toolbar_slots["measure"].isVisible() # shown right away + assert window.act_bed.iconText() == "P" + assert d.toolbar_preview.pixmap() is not None and not d.toolbar_preview.pixmap().isNull() + d.reject() # Cancel: back as it was + app.processEvents() + assert window.toolbar_slots["measure"].isVisible() and window.act_bed.iconText() == "Plate (P)" + assert window.settings.get("toolbar_hidden") == [] + + d = SettingsDialog(window.settings, window, page="toolbar") + d.toolbar_checks["home"].setChecked(False) + d.toolbar_joined_cb.setChecked(True) + d._save() # OK + app.processEvents() + assert window.settings.get("toolbar_hidden") == ["home"] and window.settings.get("toolbar_joined") + assert not window.toolbar_slots["home"].isVisible() + + d = SettingsDialog(window.settings, window, page="toolbar") + d._reset_toolbar() + d._save() + assert window.settings.get("toolbar_hidden") == [] and not window.settings.get("toolbar_joined") + assert window.toolbar_slots["home"].isVisible() diff --git a/tests/test_units_and_check.py b/tests/test_units_and_check.py index 7d0475b..46e8ff8 100644 --- a/tests/test_units_and_check.py +++ b/tests/test_units_and_check.py @@ -72,7 +72,7 @@ def test_unit_conversions(): units.set_unit("in") assert units.imperial() and units.name() == "in" assert units.length(25.4) == "1.000 in" and units.number(50.8, 1) == "2.000" - assert units.from_display(2) == pytest.approx(50.8) and units.to_display(127) == pytest.approx(5) + assert units.to_display(127) == pytest.approx(5) assert units.short(0.5) == "0.5" and units.short(2.0) == "2" and units.short(0.125) == "0.125" assert "in³" in units.volume(25.4 ** 3) units.set_unit("cm") # nonsense: millimetres diff --git a/tests/test_whats_new.py b/tests/test_whats_new.py index 363ae79..d922f4d 100644 --- a/tests/test_whats_new.py +++ b/tests/test_whats_new.py @@ -28,14 +28,14 @@ def test_only_versions_since_the_old_one_up_to_this_one(): - got = [v for v, _ in up.notes_since("0.18.0", "0.19.1", LOG)] + got = [n.version for n in up.whats_new("en", "0.18.0", "0.19.1", en_text=LOG)] assert got == ["0.19.1", "0.19.0"] def test_repository_links_become_full_urls(): - (_, notes), = up.notes_since("0.19.1", "0.20.0", LOG) - assert "](https://github.com/sevagjb/PrintView3D/blob/main/docs/TRANSLATING.md)" in notes - assert "](https://github.com/x)" in notes + notes, = up.whats_new("en", "0.19.1", "0.20.0", en_text=LOG) + assert "](https://github.com/sevagjb/PrintView3D/blob/main/docs/TRANSLATING.md)" in notes.text + assert "](https://github.com/x)" in notes.text def test_bundled_changelog_has_this_version(): diff --git a/tools/make_clips.py b/tools/make_clips.py index 8bd9dc6..38bcc45 100644 --- a/tools/make_clips.py +++ b/tools/make_clips.py @@ -8,6 +8,9 @@ xvfb-run -a -s "-screen 0 1600x1000x24" python tools/make_clips.py python tools/make_clips.py --lang en ar --out docs/assets/inside Result: docs/assets/inside/*.mp4 (with a .jpg poster each) and *.png. + +--theme light / ember makes the website's Light and Ember versions of the pictures +(name--light.webp, name--ember.webp; no videos): the website shows the one matching its theme. """ from __future__ import annotations @@ -32,11 +35,32 @@ class Capture: - def __init__(self, app, out: str): + def __init__(self, app, out: str, theme_name: str = "dark"): self.app = app self.out = out + self.theme = theme_name self.w = None + def save(self, img, name: str): + """A picture under its name in Dark; name--light.webp / name--ember.webp in the others + (dots become dashes, so the website's language plugin never reads ".ar." as a language).""" + if self.theme == "dark": + path = os.path.join(self.out, name) + if hasattr(img, "toImage"): + img.save(path) + else: + img.save(path, optimize=True) + return name + stem = os.path.splitext(name)[0].replace(".", "-") + name = f"{stem}--{self.theme}.webp" + from PIL import Image + if hasattr(img, "toImage"): + tmp = os.path.join(tempfile.mkdtemp(), "shot.png") + img.save(tmp) + img = Image.open(tmp) + img.save(os.path.join(self.out, name), "WEBP", quality=88, method=6) + return name + # ---------------------------------------------------------------- window def open(self, lang: str): from printview3d import i18n @@ -47,7 +71,7 @@ def open(self, lang: str): self.w.close() self.w.deleteLater() self.settle() - theme.apply_theme(self.app, "dark") + theme.apply_theme(self.app, self.theme) i18n.apply_language(self.app, lang) settings = Settings(os.path.join(tempfile.mkdtemp(), "settings.json")) settings.set("language", lang) @@ -94,8 +118,7 @@ def frame(self): def still(self, name: str): self.w.statusBar().clearMessage() # "Opened /path/on/this/computer/..." self.render() - self.w.grab().save(os.path.join(self.out, name)) - print(" ", name) + print(" ", self.save(self.w.grab(), name)) # ---------------------------------------------------------------- video def video(self, name: str, steps, poster_at: float = 0.0): @@ -210,7 +233,7 @@ def step(v): c.iso() w.act_wireframe.setChecked(True) c.render() - c.frame().save(os.path.join(c.out, "wireframe.png"), optimize=True) + c.save(c.frame(), "wireframe.png") print(" wireframe.png") c.off() @@ -252,7 +275,7 @@ def make_stills_029(c: Capture, lang: str): from printview3d.ui.measure import Item w = c.w sfx = "" if lang == "en" else "." + lang - cx, cy, top = 175.0, 175.0, 46.0 # see tools/make_inside_demo.py + cx, cy = 175.0, 175.0 # see tools/make_inside_demo.py # Check: overhangs (the roofs of the chamber, the channel and the pocket, seen with X-ray) and, # with a 1.2 mm nozzle, the 1 mm wall in front of the hidden pocket as a thin wall. c.off() @@ -308,7 +331,7 @@ def make_grid(c: Capture): cam.Elevation(14) cam.Zoom(0.29) c.render() - c.frame().save(os.path.join(c.out, "grid-numbers.png"), optimize=True) + c.save(c.frame(), "grid-numbers.png") print(" grid-numbers.png") c.off() @@ -318,7 +341,10 @@ def main(): ap.add_argument("--lang", nargs="+", default=["en", "ar", "nl"]) ap.add_argument("--out", default=os.path.join(ROOT, "docs", "assets", "inside")) ap.add_argument("--no-videos", action="store_true") + ap.add_argument("--theme", default="dark", choices=["dark", "light", "ember"]) a = ap.parse_args() + if a.theme != "dark": + a.no_videos = True # one set of videos (Dark) serves every theme if not os.path.isfile(DEMO): raise SystemExit("examples/demo_housing.3mf is missing: python tools/make_inside_demo.py") if shutil.which("ffmpeg") is None and not a.no_videos: @@ -327,7 +353,7 @@ def main(): subprocess.run([sys.executable, os.path.join(ROOT, "tools", "i18n.py"), "compile"], check=True, stdout=subprocess.DEVNULL) app = QApplication([]) - c = Capture(app, a.out) + c = Capture(app, a.out, a.theme) for i, lang in enumerate(a.lang): c.open(lang) if i == 0 and not a.no_videos: diff --git a/tools/make_screenshots.py b/tools/make_screenshots.py index ef51e03..5293d16 100644 --- a/tools/make_screenshots.py +++ b/tools/make_screenshots.py @@ -2,11 +2,17 @@ Run (needs a display; on Linux CI use xvfb-run): python tools/make_demo.py - python tools/make_screenshots.py + python tools/make_screenshots.py # Dark: docs/screenshot-*.png (README, website) + python tools/make_screenshots.py --theme light # the website's Light and Ember versions: + python tools/make_screenshots.py --theme ember # docs/screenshot-*--light.webp, --ember.webp + +The website shows the version matching its theme (tools/mkdocs_theme_images.py); the README on +GitHub shows the Dark pictures. --no-demo leaves the example files' thumbnails alone. """ from __future__ import annotations +import argparse import os import sys import tempfile @@ -23,7 +29,43 @@ EX = os.path.join(ROOT, "examples") +def themed_name(name: str, theme_name: str) -> str: + """screenshot-model.png -> screenshot-model--light.webp (Dark keeps the plain name). The dots of + the name become dashes, so the website's language plugin never reads ".ar." as a language.""" + if theme_name == "dark": + return name + stem, _ = os.path.splitext(name) + return f"{stem.replace('.', '-')}--{theme_name}.webp" + + +def save_image(img, path: str): + """Save a QImage/QPixmap; .webp through Pillow (smaller than PNG, sharp enough for screenshots).""" + if not path.endswith(".webp"): + img.save(path) + return + tmp = path[:-5] + ".tmp.png" + img.save(tmp) + from PIL import Image + Image.open(tmp).save(path, "WEBP", quality=88, method=6) + os.remove(tmp) + + +def plain_buttons(dialog): + """Linux's desktop theme puts icons on OK / Cancel / Apply; Windows and macOS show plain + buttons, so the screenshots do too.""" + from PySide6.QtGui import QIcon + from PySide6.QtWidgets import QDialogButtonBox + for box in dialog.findChildren(QDialogButtonBox): + for b in box.buttons(): + b.setIcon(QIcon()) + + def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--theme", default="dark", choices=["dark", "light", "ember"]) + ap.add_argument("--no-demo", action="store_true", help="don't rewrite the examples' thumbnails") + args = ap.parse_args() + dark = args.theme == "dark" app = QApplication([]) from printview3d.settings import Settings from printview3d.ui.main_window import MainWindow @@ -31,7 +73,7 @@ def main(): from printview3d.ui import theme import make_demo - theme.apply_theme(app, "dark") + theme.apply_theme(app, args.theme) from printview3d import i18n as _i18n _i18n.apply_language(app, "en") # number format as the real program has it (17,751.69) import subprocess @@ -59,7 +101,8 @@ def shot(name, widget=None): app.processEvents() w.plotter.render() app.processEvents() - (widget or w).grab().save(os.path.join(DOCS, name)) + name = themed_name(name, args.theme) + save_image((widget or w).grab(), os.path.join(DOCS, name)) print("saved", name) def camera(azim, elev, zoom=1.0): @@ -78,16 +121,17 @@ def camera(azim, elev, zoom=1.0): w.load_sync(os.path.join(EX, "demo_vase.stl")) camera(-25, -5, 1.15) shot("screenshot-model.png") - img = w.plotter.screenshot(None, return_img=True) - from PIL import Image - im = Image.fromarray(img) - h = im.height - cx = im.width // 2 - im = im.crop((cx - h // 2, 0, cx + h // 2, h)).resize((300, 300)) - import io - bio = io.BytesIO() - im.save(bio, "PNG") - make_demo.main(bio.getvalue()) + if dark and not args.no_demo: # a thumbnail for the demo G-code / 3MF + img = w.plotter.screenshot(None, return_img=True) + from PIL import Image + im = Image.fromarray(img) + h = im.height + cx = im.width // 2 + im = im.crop((cx - h // 2, 0, cx + h // 2, h)).resize((300, 300)) + import io + bio = io.BytesIO() + im.save(bio, "PNG") + make_demo.main(bio.getvalue()) # 2) G-code, cut at 60 % height w.load_sync(os.path.join(EX, "demo_vase.gcode")) @@ -120,9 +164,10 @@ def camera(azim, elev, zoom=1.0): r = w.slicer_menu.geometry() b = w.slicer_btn.mapToGlobal(QPoint(0, 0)) left, top = max(0, b.x() - 160), max(0, b.y() - 8) - screen.copy(left, top, max(r.right(), b.x() + w.slicer_btn.width()) - left + 180, - r.bottom() - top + 16).save(os.path.join(DOCS, "screenshot-slicer-menu.png")) - print("saved screenshot-slicer-menu.png") + save_image(screen.copy(left, top, max(r.right(), b.x() + w.slicer_btn.width()) - left + 180, + r.bottom() - top + 16), + os.path.join(DOCS, themed_name("screenshot-slicer-menu.png", args.theme))) + print("saved slicer menu") w.slicer_menu.close() # 6) Settings @@ -130,8 +175,10 @@ def camera(azim, elev, zoom=1.0): d.show() for _ in range(5): app.processEvents() - d.grab().save(os.path.join(DOCS, "screenshot-settings.png")) - print("saved screenshot-settings.png") + plain_buttons(d) + app.processEvents() + save_image(d.grab(), os.path.join(DOCS, themed_name("screenshot-settings.png", args.theme))) + print("saved settings") d.close() # 6b) Settings -> Updates with a (pretend) newer version on GitHub @@ -146,8 +193,10 @@ def camera(azim, elev, zoom=1.0): d.show() for _ in range(5): app.processEvents() - d.grab().save(os.path.join(DOCS, "screenshot-updates.png")) - print("saved screenshot-updates.png") + plain_buttons(d) + app.processEvents() + save_image(d.grab(), os.path.join(DOCS, themed_name("screenshot-updates.png", args.theme))) + print("saved updates") d.close() w.refresh_update_badge() settings.set("update_latest", {}) @@ -167,6 +216,10 @@ def camera(azim, elev, zoom=1.0): for _ in range(5): app.processEvents() + if not dark: # the theme pictures and FreeCAD: Dark only + w.close() + return + # 8) The same G-code in the Gray, Graphite and Light themes, plus a 2 x 2 overview w.load_sync(os.path.join(EX, "demo_vase.gcode")) w.set_layer(int(w.gcode.n_layers * 0.6)) diff --git a/tools/make_toolbar_gif.py b/tools/make_toolbar_gif.py new file mode 100644 index 0000000..719c686 --- /dev/null +++ b/tools/make_toolbar_gif.py @@ -0,0 +1,168 @@ +"""The short animation of Settings -> Toolbar for the README, the website and What's new (0.30). + +The real toolbar on top and the real Settings page below it, step by step: Check and Measure +switched off, the labels shortened to the name, then icons only, the buttons joined, and back to +the defaults. An orange frame shows what changes. No text of its own, so one GIF serves every +language. Few distinct frames and a small palette keep it small. + + xvfb-run -a -s "-screen 0 1600x1000x24" python tools/make_toolbar_gif.py +Result: docs/assets/toolbar-settings.gif +""" + +from __future__ import annotations + +import os +import sys +import tempfile + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) +os.environ.setdefault("QT_API", "pyside6") + +from PySide6.QtCore import QPoint, QRect, Qt # noqa: E402 +from PySide6.QtGui import QColor, QPainter, QPen, QPixmap # noqa: E402 +from PySide6.QtWidgets import QApplication, QDialogButtonBox, QToolButton # noqa: E402 + +OUT = os.path.join(ROOT, "docs", "assets", "toolbar-settings.gif") +WIDTH = 900 # the GIF's width (README and website show it at most this wide) +DIALOG_SCALE = 0.86 +GAP = 14 +HIGHLIGHT = QColor("#ef791b") + + +def main(): + app = QApplication([]) + from printview3d.settings import Settings + from printview3d.ui import theme + from printview3d.ui.main_window import MainWindow + from printview3d.ui.settings_dialog import SettingsDialog + from printview3d import i18n + from PIL import Image + + theme.apply_theme(app, "ember") + i18n.apply_language(app, "en") + settings = Settings(os.path.join(tempfile.mkdtemp(), "settings.json")) + w = MainWindow(settings) + w.resize(1440, 900) + w.move(0, 0) + w.show() + w.load_sync(os.path.join(ROOT, "examples", "demo_housing.3mf")) + d = SettingsDialog(settings, w, {}, page="toolbar") + d.show() + d.move(300, 60) + for box in d.findChildren(QDialogButtonBox): # plain buttons, as on Windows and macOS + for b in box.buttons(): + b.setIcon(QIcon_empty()) + + def settle(): + for _ in range(8): + app.processEvents() + for b in w.toolbar.findChildren(QToolButton): + b.setAttribute(Qt.WA_UnderMouse, False) + app.processEvents() + + def toolbar_strip() -> QPixmap: + """The toolbar without its empty middle (a short gap instead).""" + pm = w.toolbar.grab() + g = w.toolbar_spacer.geometry() + r = pm.devicePixelRatio() + keep = 24 + left = pm.copy(0, 0, round(g.x() * r), pm.height()) + right = pm.copy(round((g.right() + 1) * r), 0, pm.width() - round((g.right() + 1) * r), pm.height()) + out = QPixmap(left.width() + round(keep * r) + right.width(), pm.height()) + out.fill(Qt.transparent) + p = QPainter(out) + p.drawPixmap(0, 0, left) + p.drawPixmap(left.width(), 0, pm.copy(round(g.x() * r), 0, round(keep * r), pm.height())) + p.drawPixmap(left.width() + round(keep * r), 0, right) + p.end() + return out + + frames: list[tuple[Image.Image, int]] = [] + + def frame(widget=None, ms: int = 1300): + settle() + d._draw_toolbar_preview() + settle() + strip = toolbar_strip() + look = d.toolbar_joined_cb.parentWidget() # the Look box: the page ends below it + bottom = look.mapTo(d, QPoint(0, look.height())).y() + 16 + dlg = d.grab(QRect(0, 0, d.width(), bottom)) + s_scale = min(1.0, WIDTH / strip.width()) + strip = strip.scaledToWidth(round(strip.width() * s_scale), Qt.SmoothTransformation) + dw = round(dlg.width() * DIALOG_SCALE) + dlg_s = dlg.scaledToWidth(dw, Qt.SmoothTransformation) + H = strip.height() + GAP + dlg_s.height() + canvas = QPixmap(WIDTH, H) + canvas.fill(QColor(theme.colors()["window"])) + p = QPainter(canvas) + p.setRenderHint(QPainter.Antialiasing) + p.drawPixmap((WIDTH - strip.width()) // 2, 0, strip) + dx, dy = (WIDTH - dw) // 2, strip.height() + GAP + p.drawPixmap(dx, dy, dlg_s) + if widget is not None: # the control that just changed + tl = widget.mapTo(d, QPoint(0, 0)) + rect = QRect(dx + round(tl.x() * DIALOG_SCALE) - 4, dy + round(tl.y() * DIALOG_SCALE) - 3, + round(widget.width() * DIALOG_SCALE) + 8, round(widget.height() * DIALOG_SCALE) + 6) + p.setPen(QPen(HIGHLIGHT, 3)) + p.setBrush(Qt.NoBrush) + p.drawRoundedRect(rect, 6, 6) + p.end() + tmp = os.path.join(tempfile.mkdtemp(), "f.png") + canvas.save(tmp) + frames.append((Image.open(tmp).convert("RGB"), ms)) + + checks, combo, joined = d.toolbar_checks, d.toolbar_labels_combo, d.toolbar_joined_cb + frame(ms=1800) # as it comes + checks["check"].setChecked(False) + frame(checks["check"]) + checks["measure"].setChecked(False) + frame(checks["measure"]) + combo.setCurrentIndex(1) # Icon and name + frame(combo, 1500) + combo.setCurrentIndex(3) # Icon only + frame(combo, 1500) + joined.setChecked(True) + frame(joined, 1800) + combo.setCurrentIndex(1) # joined, with names + frame(combo, 1800) + reset = next(b for b in d.findChildren(type(d.apply_btn)) if b.text() == "Reset to defaults") + reset.click() + frame(reset, 2200) + d.reject() + + # Every frame as tall as the tallest (the toolbar's height changes a little with its look). + bg = theme.colors()["window"] + tallest = max(f.height for f, _ in frames) + frames = [(_pad(f, tallest, bg), ms) for f, ms in frames] + # One shared palette for every frame (no flicker between frames), 96 colours. + sheet = Image.new("RGB", (WIDTH, sum(f.height for f, _ in frames))) + y = 0 + for f, _ in frames: + sheet.paste(f, (0, y)) + y += f.height + pal = sheet.quantize(colors=96, method=Image.Quantize.MEDIANCUT) + imgs = [f.quantize(palette=pal, dither=Image.Dither.NONE) for f, _ in frames] + imgs[0].save(OUT, save_all=True, append_images=imgs[1:], duration=[ms for _, ms in frames], + loop=0, optimize=True, disposal=1) + print("saved", os.path.relpath(OUT, ROOT), f"{os.path.getsize(OUT) // 1024} KB,", + f"{imgs[0].width} x {imgs[0].height}, {len(imgs)} frames") + w.close() + + +def _pad(img, height: int, color: str): + from PIL import Image + if img.height == height: + return img + out = Image.new("RGB", (img.width, height), color) + out.paste(img, (0, 0)) + return out + + +def QIcon_empty(): + from PySide6.QtGui import QIcon + return QIcon() + + +if __name__ == "__main__": + main() diff --git a/tools/mkdocs_theme_images.py b/tools/mkdocs_theme_images.py new file mode 100644 index 0000000..1eb4595 --- /dev/null +++ b/tools/mkdocs_theme_images.py @@ -0,0 +1,88 @@ +"""MkDocs hook: pictures that match the website's theme. + +The app's screenshots are made in Dark (docs/screenshot-*.png, also used by the README on GitHub) +and, for the website, in Light and Ember too: name--light.webp and name--ember.webp next to them +(tools/make_screenshots.py --theme, tools/make_clips.py --theme). For every picture on a page that +has both, this hook adds the two versions beside it; docs/assets/extra.css shows only the one that +matches the theme chosen in the header (Light, Dark or Ember). Pictures without them stay as they are. + + mkdocs.yml: hooks: [tools/mkdocs_theme_images.py] +""" + +from __future__ import annotations + +import os +import posixpath +import re + +IMG = re.compile(r"]*>", re.IGNORECASE) +SRC = re.compile(r'\bsrc="([^"]+)"', re.IGNORECASE) +THEMES = ("light", "ember") + + +def variant_name(name: str, theme: str) -> str: + """screenshot-model.png -> screenshot-model--light.webp; check.ar.png -> check-ar--light.webp.""" + stem = os.path.splitext(name)[0].replace(".", "-") + return f"{stem}--{theme}.webp" + + +def _docs_file(docs_dir: str, page_url: str, src: str, languages: set[str]) -> str | None: + """The file in docs/ that an img src on a page points to (None for links elsewhere). On an + Arabic or Dutch page the language plugin has renamed check.ar.png to ar/.../check.png: then + this is the language's own file (assets/inside/check.ar.png).""" + if "://" in src or src.startswith(("data:", "/", "#")): + return None + path = posixpath.normpath(posixpath.join(posixpath.dirname(page_url), src.split("#")[0])) + parts = path.split("/") + lang = "" + if parts and parts[0] in languages: # ar/..., nl/...: a picture in that language + lang, parts = parts[0], parts[1:] + if lang: + stem, ext = os.path.splitext(parts[-1]) + localized = parts[:-1] + [f"{stem}.{lang}{ext}"] + if os.path.isfile(os.path.join(docs_dir, *localized)): + return "/".join(localized) + full = os.path.join(docs_dir, *parts) + return "/".join(parts) if os.path.isfile(full) else None + + +def _with_class(tag: str, cls: str) -> str: + if re.search(r'\bclass="', tag): + return re.sub(r'\bclass="', f'class="{cls} ', tag, count=1) + return tag[:-1].rstrip("/").rstrip() + f' class="{cls}">' + + +def on_page_content(html, page, config, files, **kwargs): + docs_dir = config["docs_dir"] + languages = set() + for plugin in config["plugins"].values(): # mkdocs-static-i18n's other languages + for lang in getattr(getattr(plugin, "config", None), "languages", None) or []: + loc = getattr(lang, "locale", None) + if loc: + languages.add(loc) + + def swap(m): + tag = m.group(0) + s = SRC.search(tag) + if not s or "pv-theme-" in tag: + return tag + src = s.group(1) + rel = _docs_file(docs_dir, page.url, src, languages) + if rel is None: + return tag + folder, name = posixpath.split(rel) + variants = {t: posixpath.join(folder, variant_name(name, t)) for t in THEMES} + if not all(os.path.isfile(os.path.join(docs_dir, *v.split("/"))) for v in variants.values()): + return tag + # The theme versions are no language's own file, so they sit at the site's root: link + # them from this page's folder (ar/use.html -> ../assets/inside/check-ar--light.webp). + here = posixpath.dirname(page.url) or "." + out = [_with_class(tag, "pv-theme-dark")] + for t in THEMES: + vsrc = posixpath.relpath(variants[t], here) + v = tag.replace(f'src="{src}"', f'src="{vsrc}"', 1) + v = re.sub(r'\bloading="[^"]*"', "", v) + out.append(_with_class(v.replace(" str: - text = "" - for attr in ("text", "title"): - f = getattr(w, attr, None) - if callable(f): - try: - text = f() or "" - except TypeError: - text = "" - if text: - break - text = text.replace("\n", " ").replace("​", "")[:45] - return f"{type(w).__name__}#{w.objectName() or '-'} {text!r}" - - -def widest_items(layout: QLayout): - """The layout's items with their minimum widths, widest first.""" - out = [] - for i in range(layout.count()): - item = layout.itemAt(i) - if item is None or item.isEmpty(): - continue - out.append((item.minimumSize().width(), item)) - return sorted(out, key=lambda t: -t[0]) - - -def follow(widget: QWidget, depth: int = 0): - pad = " " * depth - lay = widget.layout() - print(f"{pad}{widget.minimumSizeHint().width():4d} {describe(widget)}" - + (f" [layout margins {lay.contentsMargins().left()}+{lay.contentsMargins().right()}]" if lay else "")) - if lay is None: - return - items = widest_items(lay) - for w_min, item in items[:3]: # the three widest, to compare - tag = describe(item.widget()) if item.widget() else type(item.layout() or item).__name__ - print(f"{pad} -> {w_min:4d} {tag}") - if not items: - return - top = items[0][1] - nxt = top.widget() - if nxt is None and top.layout() is not None: # a nested layout: its widest widget - for w_min, sub in widest_items(top.layout()): - if sub.widget() is not None: - nxt = sub.widget() - break - if nxt is not None and depth < 8: - follow(nxt, depth + 1) - - -def main(): - code = sys.argv[1] if len(sys.argv) > 1 else "en" - px = int(sys.argv[2]) if len(sys.argv) > 2 else 21 - app = QApplication([]) - f = app.font() - f.setPixelSize(px) - app.setFont(f) - from printview3d import i18n - import tempfile - sys.path.insert(0, HERE) - import i18n as tool # tools/i18n.py - tool.cmd_compile() - i18n.apply_language(app, code) - from printview3d.settings import Settings - from printview3d.ui import main_window as mw - w = mw.MainWindow(Settings(os.path.join(tempfile.mkdtemp(), "settings.json"))) - w.resize(1280, 760) - w.show() - w.load_sync(os.path.join(ROOT, "tests", "data", "busy_project.3mf")) - for _ in range(30): - app.processEvents() - s = app.primaryScreen() - print(f"\nPrintView3D panel width probe: language {code}, font {px} px " - f"({app.font().family()}, {QFontMetrics(app.font()).height()} px high), style {app.style().name()}, " - f"{s.logicalDotsPerInch():.0f} dpi, scale {s.devicePixelRatio():g}") - print(f"panel {w.side.width()} px, needs {w.side.minimumSizeHint().width()} px, view {w.side.parentWidget().width()} px\n") - follow(w.side) - print("\nAll visible widgets that need more than 200 px:") - rows = sorted(((c.minimumSizeHint().width(), describe(c)) for c in w.side.findChildren(QWidget) - if c.isVisible() and c.minimumSizeHint().width() > 200), reverse=True) - for wd, d in rows[:15]: - print(f" {wd:4d} {d}") - w.close() - - -if __name__ == "__main__": - main() diff --git a/translations/printview3d.ts b/translations/printview3d.ts index 5040477..0d35799 100644 --- a/translations/printview3d.ts +++ b/translations/printview3d.ts @@ -149,6 +149,7 @@ + Fit Toolbar button: zoom the 3D view so the whole model fits. @@ -645,6 +646,45 @@ Theme: {name} + + + + Plate + Toolbar toggle: show or hide the printer's build plate / bed grid. + + + + + + Check + Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. + + + + + + View + Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). + + + + + + Measure + + + + + + Theme + Settings: the colour theme of the program (dark, gray, light). + + + + + Customize toolbar… + + Preview picture @@ -687,11 +727,13 @@ + Menu + Home Toolbar button with a house icon: closes the open file and goes back to the start page. @@ -728,24 +770,11 @@ Last item in the menu of the Export STL button: opens the setting that decides what clicking the button itself does. - - - - Plate - Toolbar toggle: show or hide the printer's build plate / bed grid. - - Show / hide the build plate grid (arrow: plate size) - - - Check - Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. - - Check the model before printing: overhangs, thin walls and problems in the mesh @@ -863,12 +892,6 @@ How the model is drawn: wireframe, X-ray, clip - - - View - Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). - - Reset view @@ -940,11 +963,6 @@ Measure on the model: distance, angle, circle - - - Measure - - Snapping @@ -988,6 +1006,7 @@ + Screenshot @@ -1617,6 +1636,7 @@ + Open in slicer @@ -1959,12 +1979,6 @@ Language and units - - - Theme - Settings: the colour theme of the program (dark, gray, light). - - System follows the light or dark setting of your computer. @@ -2015,6 +2029,11 @@ Appearance + + + Toolbar + + installed @@ -2361,6 +2380,66 @@ Keyboard and mouse + + + Choose which buttons the toolbar shows. A hidden button keeps its key, and everything stays in the ☰ menu. The order never changes. + + + + + Buttons, in toolbar order + + + + + Open + + + + + {name} · always shown + + + + + Look + + + + + Icon, name and key + + + + + Icon and name + + + + + Icon and key + + + + + Icon only + + + + + View buttons show: + + + + + Join the view buttons into one group + + + + + Reset to defaults + + Skip this version @@ -2502,11 +2581,6 @@ Default: {key} - - - Reset to defaults - - Puts every key back to the one PrintView3D comes with. diff --git a/translations/printview3d_ar.ts b/translations/printview3d_ar.ts index 3191f0b..c241b7e 100644 --- a/translations/printview3d_ar.ts +++ b/translations/printview3d_ar.ts @@ -149,6 +149,7 @@ + Fit Toolbar button: zoom the 3D view so the whole model fits. ملاءمة @@ -645,6 +646,45 @@ Theme: {name} السمة: {name} + + + + Plate + Toolbar toggle: show or hide the printer's build plate / bed grid. + اللوحة + + + + + Check + Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. + فحص + + + + + View + Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). + العرض + + + + + Measure + قياس + + + + + Theme + Settings: the colour theme of the program (dark, gray, light). + السمة + + + + Customize toolbar… + تخصيص شريط الأدوات… + Preview picture @@ -687,11 +727,13 @@ + Menu القائمة + Home Toolbar button with a house icon: closes the open file and goes back to the start page. الرئيسية @@ -728,24 +770,11 @@ Last item in the menu of the Export STL button: opens the setting that decides what clicking the button itself does. تغيير ما تفعله النقرة… - - - - Plate - Toolbar toggle: show or hide the printer's build plate / bed grid. - اللوحة - Show / hide the build plate grid (arrow: plate size) إظهار / إخفاء شبكة لوحة الطباعة (السهم: حجم اللوحة) - - - Check - Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. - فحص - Check the model before printing: overhangs, thin walls and problems in the mesh @@ -863,12 +892,6 @@ How the model is drawn: wireframe, X-ray, clip طريقة رسم النموذج: الإطار السلكي، الأشعة السينية، القطع - - - View - Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). - العرض - Reset view @@ -940,11 +963,6 @@ Measure on the model: distance, angle, circle القياس على النموذج: المسافة، الزاوية، الدائرة - - - Measure - قياس - Snapping @@ -988,6 +1006,7 @@ + Screenshot لقطة شاشة @@ -1617,6 +1636,7 @@ + Open in slicer فتح في برنامج التقطيع @@ -1959,12 +1979,6 @@ Language and units اللغة والوحدات - - - Theme - Settings: the colour theme of the program (dark, gray, light). - السمة - System follows the light or dark setting of your computer. @@ -2015,6 +2029,11 @@ Appearance المظهر + + + Toolbar + شريط الأدوات + installed @@ -2361,6 +2380,66 @@ Keyboard and mouse لوحة المفاتيح والماوس + + + Choose which buttons the toolbar shows. A hidden button keeps its key, and everything stays in the ☰ menu. The order never changes. + اختر الأزرار التي يعرضها شريط الأدوات. يحتفظ الزر المخفي باختصاره، ويبقى كل شيء في القائمة ☰. لا يتغيّر الترتيب أبدًا. + + + + Buttons, in toolbar order + الأزرار، بترتيب شريط الأدوات + + + + Open + فتح + + + + {name} · always shown + {name} · ظاهر دائمًا + + + + Look + الشكل + + + + Icon, name and key + الأيقونة والاسم والاختصار + + + + Icon and name + الأيقونة والاسم + + + + Icon and key + الأيقونة والاختصار + + + + Icon only + الأيقونة فقط + + + + View buttons show: + تعرض أزرار العرض: + + + + Join the view buttons into one group + ضمّ أزرار العرض في مجموعة واحدة + + + + Reset to defaults + استعادة الافتراضيات + Skip this version @@ -2502,11 +2581,6 @@ Default: {key} الافتراضي: {key} - - - Reset to defaults - استعادة الافتراضيات - Puts every key back to the one PrintView3D comes with. @@ -2893,6 +2967,10 @@ {v} cm³ {v} سم³ + + Buttons (left to right) + الأزرار (من اليسار إلى اليمين) + Remember the colours of the Check section تذكّر ألوان قسم الفحص diff --git a/translations/printview3d_de.ts b/translations/printview3d_de.ts index 74b4eeb..8a4230e 100644 --- a/translations/printview3d_de.ts +++ b/translations/printview3d_de.ts @@ -149,6 +149,7 @@ + Fit Toolbar button: zoom the 3D view so the whole model fits. Einpassen @@ -645,6 +646,45 @@ Theme: {name} Design: {name} + + + + Plate + Toolbar toggle: show or hide the printer's build plate / bed grid. + Druckbett + + + + + Check + Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. + Prüfen + + + + + View + Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). + Ansicht + + + + + Measure + Messen + + + + + Theme + Settings: the colour theme of the program (dark, gray, light). + Design + + + + Customize toolbar… + Werkzeugleiste anpassen… + Preview picture @@ -687,11 +727,13 @@ + Menu Menü + Home Toolbar button with a house icon: closes the open file and goes back to the start page. Start @@ -728,24 +770,11 @@ Last item in the menu of the Export STL button: opens the setting that decides what clicking the button itself does. Ändern, was ein Klick macht… - - - - Plate - Toolbar toggle: show or hide the printer's build plate / bed grid. - Druckbett - Show / hide the build plate grid (arrow: plate size) Raster des Druckbetts ein- / ausblenden (Pfeil: Druckbettgröße) - - - Check - Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. - Prüfen - Check the model before printing: overhangs, thin walls and problems in the mesh @@ -863,12 +892,6 @@ How the model is drawn: wireframe, X-ray, clip Wie das Modell gezeichnet wird: Drahtgitter, Röntgen, Schnitt - - - View - Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). - Ansicht - Reset view @@ -940,11 +963,6 @@ Measure on the model: distance, angle, circle Am Modell messen: Abstand, Winkel, Kreis - - - Measure - Messen - Snapping @@ -988,6 +1006,7 @@ + Screenshot Bildschirmfoto @@ -1617,6 +1636,7 @@ + Open in slicer Im Slicer öffnen @@ -1959,12 +1979,6 @@ Language and units Sprache und Einheiten - - - Theme - Settings: the colour theme of the program (dark, gray, light). - Design - System follows the light or dark setting of your computer. @@ -2015,6 +2029,11 @@ Appearance Darstellung + + + Toolbar + Werkzeugleiste + installed @@ -2361,6 +2380,66 @@ Keyboard and mouse Tastatur und Maus + + + Choose which buttons the toolbar shows. A hidden button keeps its key, and everything stays in the ☰ menu. The order never changes. + Wählen Sie, welche Schaltflächen die Werkzeugleiste zeigt. Eine ausgeblendete Schaltfläche behält ihre Taste, und alles bleibt im ☰-Menü. Die Reihenfolge ändert sich nie. + + + + Buttons, in toolbar order + Schaltflächen, in der Reihenfolge der Werkzeugleiste + + + + Open + Öffnen + + + + {name} · always shown + {name} · immer sichtbar + + + + Look + Aussehen + + + + Icon, name and key + Symbol, Name und Taste + + + + Icon and name + Symbol und Name + + + + Icon and key + Symbol und Taste + + + + Icon only + Nur Symbol + + + + View buttons show: + Ansichtsschaltflächen zeigen: + + + + Join the view buttons into one group + Ansichtsschaltflächen zu einer Gruppe verbinden + + + + Reset to defaults + Auf Standard zurücksetzen + Skip this version @@ -2502,11 +2581,6 @@ Default: {key} Standard: {key} - - - Reset to defaults - Auf Standard zurücksetzen - Puts every key back to the one PrintView3D comes with. @@ -2893,6 +2967,10 @@ {v} cm³ {v} cm³ + + Buttons (left to right) + Schaltflächen (von links nach rechts) + Remember the colours of the Check section Die Farben des Prüfen-Bereichs merken diff --git a/translations/printview3d_es.ts b/translations/printview3d_es.ts index 2efbe3b..24efe3a 100644 --- a/translations/printview3d_es.ts +++ b/translations/printview3d_es.ts @@ -149,6 +149,7 @@ + Fit Toolbar button: zoom the 3D view so the whole model fits. Ajustar @@ -645,6 +646,45 @@ Theme: {name} Tema: {name} + + + + Plate + Toolbar toggle: show or hide the printer's build plate / bed grid. + Cama + + + + + Check + Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. + Comprobar + + + + + View + Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). + Vista + + + + + Measure + Medir + + + + + Theme + Settings: the colour theme of the program (dark, gray, light). + Tema + + + + Customize toolbar… + Personalizar la barra de herramientas… + Preview picture @@ -687,11 +727,13 @@ + Menu Menú + Home Toolbar button with a house icon: closes the open file and goes back to the start page. Inicio @@ -728,24 +770,11 @@ Last item in the menu of the Export STL button: opens the setting that decides what clicking the button itself does. Cambiar lo que hace un clic… - - - - Plate - Toolbar toggle: show or hide the printer's build plate / bed grid. - Cama - Show / hide the build plate grid (arrow: plate size) Mostrar / ocultar la cuadrícula de la cama (flecha: tamaño de la cama) - - - Check - Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. - Comprobar - Check the model before printing: overhangs, thin walls and problems in the mesh @@ -863,12 +892,6 @@ How the model is drawn: wireframe, X-ray, clip Cómo se dibuja el modelo: alambre, rayos X, corte - - - View - Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). - Vista - Reset view @@ -940,11 +963,6 @@ Measure on the model: distance, angle, circle Medir en el modelo: distancia, ángulo, círculo - - - Measure - Medir - Snapping @@ -988,6 +1006,7 @@ + Screenshot Captura de pantalla @@ -1617,6 +1636,7 @@ + Open in slicer Abrir en el laminador @@ -1959,12 +1979,6 @@ Language and units Idioma y unidades - - - Theme - Settings: the colour theme of the program (dark, gray, light). - Tema - System follows the light or dark setting of your computer. @@ -2015,6 +2029,11 @@ Appearance Apariencia + + + Toolbar + Barra de herramientas + installed @@ -2361,6 +2380,66 @@ Keyboard and mouse Teclado y ratón + + + Choose which buttons the toolbar shows. A hidden button keeps its key, and everything stays in the ☰ menu. The order never changes. + Elige qué botones muestra la barra de herramientas. Un botón oculto conserva su tecla y todo sigue en el menú ☰. El orden nunca cambia. + + + + Buttons, in toolbar order + Botones, en el orden de la barra + + + + Open + Abrir + + + + {name} · always shown + {name} · siempre visible + + + + Look + Aspecto + + + + Icon, name and key + Icono, nombre y tecla + + + + Icon and name + Icono y nombre + + + + Icon and key + Icono y tecla + + + + Icon only + Solo icono + + + + View buttons show: + Los botones de vista muestran: + + + + Join the view buttons into one group + Unir los botones de vista en un grupo + + + + Reset to defaults + Restablecer valores predeterminados + Skip this version @@ -2502,11 +2581,6 @@ Default: {key} Predeterminada: {key} - - - Reset to defaults - Restablecer valores predeterminados - Puts every key back to the one PrintView3D comes with. @@ -2893,6 +2967,10 @@ {v} cm³ {v} cm³ + + Buttons (left to right) + Botones (de izquierda a derecha) + Remember the colours of the Check section Recordar los colores de la sección Comprobar diff --git a/translations/printview3d_fr.ts b/translations/printview3d_fr.ts index 84805a1..fb45008 100644 --- a/translations/printview3d_fr.ts +++ b/translations/printview3d_fr.ts @@ -149,6 +149,7 @@ + Fit Toolbar button: zoom the 3D view so the whole model fits. Ajuster @@ -645,6 +646,45 @@ Theme: {name} Thème : {name} + + + + Plate + Toolbar toggle: show or hide the printer's build plate / bed grid. + Plateau + + + + + Check + Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. + Vérifier + + + + + View + Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). + Affichage + + + + + Measure + Mesurer + + + + + Theme + Settings: the colour theme of the program (dark, gray, light). + Thème + + + + Customize toolbar… + Personnaliser la barre d'outils… + Preview picture @@ -687,11 +727,13 @@ + Menu Menu + Home Toolbar button with a house icon: closes the open file and goes back to the start page. Accueil @@ -728,24 +770,11 @@ Last item in the menu of the Export STL button: opens the setting that decides what clicking the button itself does. Modifier l’action du clic… - - - - Plate - Toolbar toggle: show or hide the printer's build plate / bed grid. - Plateau - Show / hide the build plate grid (arrow: plate size) Afficher / masquer la grille du plateau (flèche : taille du plateau) - - - Check - Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. - Vérifier - Check the model before printing: overhangs, thin walls and problems in the mesh @@ -863,12 +892,6 @@ How the model is drawn: wireframe, X-ray, clip Comment le modèle est dessiné : filaire, rayons X, coupe - - - View - Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). - Affichage - Reset view @@ -940,11 +963,6 @@ Measure on the model: distance, angle, circle Mesurer sur le modèle : distance, angle, cercle - - - Measure - Mesurer - Snapping @@ -988,6 +1006,7 @@ + Screenshot Capture d'écran @@ -1617,6 +1636,7 @@ + Open in slicer Ouvrir dans le trancheur @@ -1959,12 +1979,6 @@ Language and units Langue et unités - - - Theme - Settings: the colour theme of the program (dark, gray, light). - Thème - System follows the light or dark setting of your computer. @@ -2015,6 +2029,11 @@ Appearance Apparence + + + Toolbar + Barre d'outils + installed @@ -2361,6 +2380,66 @@ Keyboard and mouse Clavier et souris + + + Choose which buttons the toolbar shows. A hidden button keeps its key, and everything stays in the ☰ menu. The order never changes. + Choisissez les boutons affichés dans la barre d'outils. Un bouton masqué garde son raccourci, et tout reste dans le menu ☰. L'ordre ne change jamais. + + + + Buttons, in toolbar order + Boutons, dans l'ordre de la barre d'outils + + + + Open + Ouvrir + + + + {name} · always shown + {name} · toujours affiché + + + + Look + Apparence + + + + Icon, name and key + Icône, nom et raccourci + + + + Icon and name + Icône et nom + + + + Icon and key + Icône et raccourci + + + + Icon only + Icône seule + + + + View buttons show: + Les boutons d'affichage montrent : + + + + Join the view buttons into one group + Regrouper les boutons d'affichage + + + + Reset to defaults + Rétablir les valeurs par défaut + Skip this version @@ -2502,11 +2581,6 @@ Default: {key} Par défaut : {key} - - - Reset to defaults - Rétablir les valeurs par défaut - Puts every key back to the one PrintView3D comes with. @@ -2893,6 +2967,10 @@ {v} cm³ {v} cm³ + + Buttons (left to right) + Boutons (de gauche à droite) + Remember the colours of the Check section Retenir les couleurs de la section Vérifier diff --git a/translations/printview3d_nl.ts b/translations/printview3d_nl.ts index e3c9211..3bec073 100644 --- a/translations/printview3d_nl.ts +++ b/translations/printview3d_nl.ts @@ -149,6 +149,7 @@ + Fit Toolbar button: zoom the 3D view so the whole model fits. Passend @@ -645,6 +646,45 @@ Theme: {name} Thema: {name} + + + + Plate + Toolbar toggle: show or hide the printer's build plate / bed grid. + Plaat + + + + + Check + Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. + Controleren + + + + + View + Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). + Weergave + + + + + Measure + Meten + + + + + Theme + Settings: the colour theme of the program (dark, gray, light). + Thema + + + + Customize toolbar… + Werkbalk aanpassen… + Preview picture @@ -687,11 +727,13 @@ + Menu Menu + Home Toolbar button with a house icon: closes the open file and goes back to the start page. Start @@ -728,24 +770,11 @@ Last item in the menu of the Export STL button: opens the setting that decides what clicking the button itself does. Wijzigen wat een klik doet… - - - - Plate - Toolbar toggle: show or hide the printer's build plate / bed grid. - Plaat - Show / hide the build plate grid (arrow: plate size) Raster van de printplaat tonen / verbergen (pijltje: plaatformaat) - - - Check - Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. - Controleren - Check the model before printing: overhangs, thin walls and problems in the mesh @@ -863,12 +892,6 @@ How the model is drawn: wireframe, X-ray, clip Hoe het model wordt getekend: draadmodel, röntgen, afsnijden - - - View - Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). - Weergave - Reset view @@ -940,11 +963,6 @@ Measure on the model: distance, angle, circle Meten op het model: afstand, hoek, cirkel - - - Measure - Meten - Snapping @@ -988,6 +1006,7 @@ + Screenshot Schermafbeelding @@ -1617,6 +1636,7 @@ + Open in slicer Openen in slicer @@ -1959,12 +1979,6 @@ Language and units Taal en eenheden - - - Theme - Settings: the colour theme of the program (dark, gray, light). - Thema - System follows the light or dark setting of your computer. @@ -2015,6 +2029,11 @@ Appearance Uiterlijk + + + Toolbar + Werkbalk + installed @@ -2361,6 +2380,66 @@ Keyboard and mouse Toetsenbord en muis + + + Choose which buttons the toolbar shows. A hidden button keeps its key, and everything stays in the ☰ menu. The order never changes. + Kies welke knoppen de werkbalk toont. Een verborgen knop houdt zijn toets, en alles blijft in het ☰-menu. De volgorde verandert nooit. + + + + Buttons, in toolbar order + Knoppen, in de volgorde van de werkbalk + + + + Open + Openen + + + + {name} · always shown + {name} · altijd zichtbaar + + + + Look + Uiterlijk + + + + Icon, name and key + Pictogram, naam en toets + + + + Icon and name + Pictogram en naam + + + + Icon and key + Pictogram en toets + + + + Icon only + Alleen pictogram + + + + View buttons show: + Weergaveknoppen tonen: + + + + Join the view buttons into one group + Weergaveknoppen samenvoegen tot één groep + + + + Reset to defaults + Standaardwaarden herstellen + Skip this version @@ -2502,11 +2581,6 @@ Default: {key} Standaard: {key} - - - Reset to defaults - Standaardwaarden herstellen - Puts every key back to the one PrintView3D comes with. @@ -2893,6 +2967,10 @@ {v} cm³ {v} cm³ + + Buttons (left to right) + Knoppen (van links naar rechts) + Remember the colours of the Check section De kleuren van het onderdeel Controleren onthouden diff --git a/translations/printview3d_ro.ts b/translations/printview3d_ro.ts index c52390e..27e0862 100644 --- a/translations/printview3d_ro.ts +++ b/translations/printview3d_ro.ts @@ -149,6 +149,7 @@ + Fit Toolbar button: zoom the 3D view so the whole model fits. Încadrează @@ -645,6 +646,45 @@ Theme: {name} Temă: {name} + + + + Plate + Toolbar toggle: show or hide the printer's build plate / bed grid. + Placă + + + + + Check + Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. + Verifică + + + + + View + Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). + Vizualizare + + + + + Measure + Măsoară + + + + + Theme + Settings: the colour theme of the program (dark, gray, light). + Temă + + + + Customize toolbar… + Personalizează bara de instrumente… + Preview picture @@ -687,11 +727,13 @@ + Menu Meniu + Home Toolbar button with a house icon: closes the open file and goes back to the start page. Acasă @@ -728,24 +770,11 @@ Last item in the menu of the Export STL button: opens the setting that decides what clicking the button itself does. Schimbă ce face un clic… - - - - Plate - Toolbar toggle: show or hide the printer's build plate / bed grid. - Placă - Show / hide the build plate grid (arrow: plate size) Arată / ascunde grila plăcii (săgeată: dimensiunea plăcii) - - - Check - Toolbar button: check the model before printing (overhangs, thin walls, mesh problems). A verb or a short noun. - Verifică - Check the model before printing: overhangs, thin walls and problems in the mesh @@ -863,12 +892,6 @@ How the model is drawn: wireframe, X-ray, clip Cum este desenat modelul: wireframe, raze X, tăiere - - - View - Toolbar menu: how the model is drawn (wireframe, X-ray, clip from the top). - Vizualizare - Reset view @@ -940,11 +963,6 @@ Measure on the model: distance, angle, circle Măsoară pe model: distanță, unghi, cerc - - - Measure - Măsoară - Snapping @@ -988,6 +1006,7 @@ + Screenshot Captură de ecran @@ -1617,6 +1636,7 @@ + Open in slicer Deschide în slicer @@ -1959,12 +1979,6 @@ Language and units Limbă și unități - - - Theme - Settings: the colour theme of the program (dark, gray, light). - Temă - System follows the light or dark setting of your computer. @@ -2015,6 +2029,11 @@ Appearance Aspect + + + Toolbar + Bara de instrumente + installed @@ -2361,6 +2380,66 @@ Keyboard and mouse Tastatură și mouse + + + Choose which buttons the toolbar shows. A hidden button keeps its key, and everything stays in the ☰ menu. The order never changes. + Alege ce butoane arată bara de instrumente. Un buton ascuns își păstrează tasta, iar totul rămâne în meniul ☰. Ordinea nu se schimbă niciodată. + + + + Buttons, in toolbar order + Butoane, în ordinea din bara de instrumente + + + + Open + Deschide + + + + {name} · always shown + {name} · afișat mereu + + + + Look + Aspect + + + + Icon, name and key + Pictogramă, nume și tastă + + + + Icon and name + Pictogramă și nume + + + + Icon and key + Pictogramă și tastă + + + + Icon only + Doar pictogramă + + + + View buttons show: + Butoanele de vizualizare arată: + + + + Join the view buttons into one group + Unește butoanele de vizualizare într-un grup + + + + Reset to defaults + Revino la valorile implicite + Skip this version @@ -2502,11 +2581,6 @@ Default: {key} Implicit: {key} - - - Reset to defaults - Revino la valorile implicite - Puts every key back to the one PrintView3D comes with. @@ -2893,6 +2967,10 @@ {v} cm³ {v} cm³ + + Buttons (left to right) + Butoane (de la stânga la dreapta) + Remember the colours of the Check section Ține minte culorile secțiunii Verifică