Compare commits

...

23 Commits

Author SHA1 Message Date
forbes
4bf74cf339 fix(build): fix DlgSettingsGeneral::applyMenuIconSize visibility and namespace
Some checks failed
Build and Test / build (pull_request) Has been cancelled
- Move applyMenuIconSize to public access so StartupProcess can call it
- Add Dialog:: namespace qualifier in StartupProcess.cpp
2026-02-08 17:04:41 -06:00
3b07a0f99b Merge pull request 'chore: update silo submodule with SSE, start panel, and activity pane' (#48) from chore/update-silo-submodule into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #48
2026-02-08 22:26:36 +00:00
forbes
9cabb29824 chore: update silo submodule to latest main
Some checks failed
Build and Test / build (pull_request) Has been cancelled
New features:
- SSE reconnect with exponential backoff and terminal state handling
- Silo_Diag connection diagnostics command
- Server mode UI reflection in client
- Silo-aware start panel
- Database Activity pane with comments, interaction, and badges
2026-02-08 16:25:59 -06:00
793e6bdd49 Merge pull request 'fix(gui): UI appearance polish - Wayland scaling, menu icon size pref, dialog cleanup' (#47) from fix/ui-appearance-polish into main
Some checks failed
Build and Test / build (push) Failing after 14m32s
Reviewed-on: #47
2026-02-08 21:37:03 +00:00
b6ab14cc6f Merge branch 'main' into fix/ui-appearance-polish
Some checks failed
Build and Test / build (pull_request) Failing after 17m32s
2026-02-08 21:36:51 +00:00
da46073d68 Merge pull request 'feat: expose version to Python and add update checker (#28, #29)' (#45) from feat/update-checker into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #45
2026-02-08 21:36:40 +00:00
forbes
262dfa583d fix(defaults): tune Document, Selection, and Notification preference defaults
Some checks failed
Build and Test / build (pull_request) Failing after 12m28s
Document (fixes #37):
- Increase max undo/redo steps from 20 to 50
- Reduce auto-save interval from 15 to 5 minutes
- Increase max backup files from 1 to 3
- Set license to Other with blank URL (removes Wikipedia link)

Selection (fixes #38):
- Enable preselect-on-tree-hover (PreSelection)
- Enable auto-switch to 3D view (SyncView)
- Enable auto-expand tree item (SyncSelection)

Notification Area (fixes #40):
- Reduce max notification list from 1000 to 100
- Reduce max concurrent popups from 15 to 3
- Reduce bubble width from 800px to 400px
- Reduce max popup duration from 20s to 10s
- Reduce min popup duration from 5s to 3s
2026-02-08 13:36:57 -06:00
forbes
cc5ba638d1 fix(gui): UI appearance polish — Wayland scaling, menu icon size pref, dialog cleanup
Some checks failed
Build and Test / build (pull_request) Failing after 13m13s
- Fix hazy text on Wayland by setting QT_SCALE_FACTOR_ROUNDING_POLICY=RoundPreferFloor
  and QT_ENABLE_HIGHDPI_SCALING=1 in deb and AppImage launchers; remove forced
  QT_QPA_PLATFORM=xcb from AppImage to allow native Wayland (fixes #33)

- Add menu icon size preference selector (Small/Medium/Large/Extra large) in
  General preferences, applied via QMenu stylesheet at startup and on save (fixes #46)

- Replace verbose theme customization banner with concise 'Kindred Create Theme'
  label in UI preferences; split Overlay group into 'Panel Visibility' and
  'Overlay Interaction' sections (fixes #35)

- Remove Addon Manager branding label from General preferences, change recent
  file list default from 4 to 10 (fixes #36)
2026-02-08 13:27:22 -06:00
forbes
35302154ae feat: expose version to Python and add update checker (#28, #29)
All checks were successful
Build and Test / build (pull_request) Successful in 1h11m28s
Issue #28: Add version.py.in CMake template that injects
KINDRED_CREATE_VERSION at build time, making the Kindred Create
version available to Python code via 'from version import VERSION'.

Issue #29: Add update_checker.py that queries the Gitea releases
API on startup (10s deferred) to check for newer versions. Uses
stdlib urllib only, 5s timeout, never blocks the UI. Respects
user preferences for check interval, enable/disable, and skipped
versions. Logs results to Console for now — UI notification will
follow in issue #30.

Closes #28
Closes #29
2026-02-08 13:11:08 -06:00
forbes
be7f1d9221 fix: update silo submodule — fix origin registration import
All checks were successful
Build and Test / build (push) Successful in 1h7m24s
Silo origin was not appearing in the origin selector because
silo_origin.py used a relative import (from .silo_commands) which
fails since the directory is not a Python package. Updated submodule
includes the fix to use absolute imports.
2026-02-08 10:36:14 -06:00
forbes
10b5c9d584 fix(gui): wire Manage Origins dialog to Silo_Settings command
Some checks failed
Build and Test / build (push) Has been cancelled
Replace stub QMessageBox in Add Silo and Edit Origin buttons with
actual calls to the Silo_Settings command, so users can configure
their Silo connection from the origin manager dialog.
2026-02-08 10:31:41 -06:00
forbes
ee839c23b8 fix(ci): add error handling to release creation API calls
Some checks failed
Build and Test / build (push) Has been cancelled
Release Build / build-linux (push) Successful in 1h26m59s
Release Build / publish-release (push) Has been cancelled
Print HTTP status and response body on failure instead of crashing
with a cryptic KeyError when the Gitea API returns an error.
2026-02-08 10:22:26 -06:00
forbes
67f825c305 fix(ci): filter dereferenced tag refs from git ls-remote output
Some checks failed
Build and Test / build (push) Successful in 1h7m46s
Release Build / build-linux (push) Successful in 1h25m16s
Release Build / publish-release (push) Failing after 5m17s
2026-02-07 22:22:55 -06:00
440df2a9be Merge pull request 'fix(gui): merge Silo toolbar into File toolbar via origin system (#65)' (#27) from fix/merge-silo-toolbar into main
Some checks failed
Build and Test / build (push) Failing after 1m33s
Release Build / build-linux (push) Failing after 1m58s
Release Build / publish-release (push) Has been skipped
2026-02-08 03:29:24 +00:00
forbes
1750949afd fix(gui): merge Silo toolbar into File toolbar via origin system (#65)
Some checks failed
Build and Test / build (pull_request) Failing after 1m41s
- Remove separate Silo workbench toolbar (now redundant)
- Remove SiloMenuManipulator (Std commands already delegate to origins)
- Remove Silo_ToggleMode (origin selector handles mode switching)
- Register Silo origin at startup via Create module
- Update docs to reflect unified origin architecture
2026-02-07 21:29:05 -06:00
b2c6fc2ebc Merge pull request 'fix(silo): workbench bug fixes and submodule updates' (#26) from fix/silo-workbench-bugs into main
Some checks failed
Build and Test / build (push) Failing after 1m52s
2026-02-08 03:11:07 +00:00
forbes
d0c67455dc chore: update ztools and OndselSolver submodules
Some checks failed
Build and Test / build (pull_request) Failing after 3m25s
2026-02-07 21:01:00 -06:00
forbes
62906b0269 test(gui): add unit tests for OriginManager and LocalFileOrigin
Some checks failed
Build and Test / build (push) Has been cancelled
Add 20 GTest cases covering the Kindred Origin system:

- LocalFileOriginTest: identity methods and capability flags
- OriginManagerTest: registration lifecycle, current origin
  selection, signal emissions, and fallback behavior
- OriginManagerDocTest: document ownership via SiloItemId
  property detection, origin association, and null safety

Uses MockOrigin stub for testing OriginManager without real
PLM backends.
2026-02-07 20:56:08 -06:00
99bc7629e7 Merge pull request 'fix(gui): widen origin selector widget and update silo submodule' (#25) from fix/silo-workbench-bugs into main
All checks were successful
Build and Test / build (push) Successful in 1h3m36s
Reviewed-on: #25
2026-02-07 20:34:56 +00:00
forbes
d95c850b7b fix(gui): widen origin selector widget and update silo submodule
Some checks failed
Build and Test / build (pull_request) Has been cancelled
- Increase OriginSelectorWidget max width from 120 to 160px for longer
  origin names
- Set explicit SizePolicy::Preferred for better toolbar layout
- Update silo submodule to fix/silo-workbench-bugs with auth and menu fixes
2026-02-07 14:34:23 -06:00
1f7dae4f11 Merge pull request 'art: update kindred icon set' (#24) from art/update-kindred-icons into main
All checks were successful
Build and Test / build (push) Successful in 1h5m8s
Reviewed-on: #24
2026-02-07 16:45:30 +00:00
1d7e4e2eef Merge pull request 'fix(ci): fetch only latest tag, add patchelf dep, update docs' (#23) from docs/update-ci-and-overview into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #23
2026-02-07 16:45:20 +00:00
forbes
fe50b1595e art: update kindred icon set
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Update Document, DrawStyle, Link, PartDesign, and document-* SVGs.
Remove DrawStyleHiddenLine.svg.
2026-02-07 10:44:24 -06:00
48 changed files with 1627 additions and 262 deletions

View File

@@ -49,7 +49,7 @@ jobs:
- name: Fetch latest tag (for git describe)
run: |
latest_tag=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | head -n1 | awk '{print $2}')
latest_tag=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | grep -v '\^{}' | head -n1 | awk '{print $2}')
if [ -n "$latest_tag" ]; then
git fetch --no-recurse-submodules --force --depth=1 origin "+${latest_tag}:${latest_tag}"
fi

View File

@@ -59,7 +59,7 @@ jobs:
- name: Fetch latest tag (for git describe)
run: |
latest_tag=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | head -n1 | awk '{print $2}')
latest_tag=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | grep -v '\^{}' | head -n1 | awk '{print $2}')
if [ -n "$latest_tag" ]; then
git fetch --no-recurse-submodules --force --depth=1 origin "+${latest_tag}:${latest_tag}"
fi
@@ -393,21 +393,34 @@ jobs:
fi
# Create release
release_id=$(curl -s -X POST \
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$PAYLOAD" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases" | \
python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
"${GITEA_URL}/api/v1/repos/${REPO}/releases")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
echo "::error::Failed to create release (HTTP ${http_code}): ${body}"
exit 1
fi
release_id=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Created release ${release_id}"
# Upload assets
for file in release/*; do
filename=$(basename "$file")
echo "Uploading ${filename}..."
curl -s -X POST \
upload_resp=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@${file}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${release_id}/assets?name=${filename}"
echo " done."
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${release_id}/assets?name=${filename}")
upload_code=$(echo "$upload_resp" | tail -1)
if [ "$upload_code" -lt 200 ] || [ "$upload_code" -ge 300 ]; then
echo "::warning::Failed to upload ${filename} (HTTP ${upload_code})"
else
echo " done."
fi
done

View File

@@ -17,8 +17,8 @@ FreeCAD startup
│ └─ exec(mods/silo/freecad/InitGui.py)
│ └─ registers SiloWorkbench
└─ Deferred setup (QTimer):
├─ 1500ms: _setup_silo_auth_panel() → "Database Auth" dock
├─ 2000ms: _setup_silo_menu() → SiloMenuManipulator
├─ 1500ms: _register_silo_origin() registers Silo FileOrigin
├─ 2000ms: _setup_silo_auth_panel() → "Database Auth" dock
├─ 3000ms: _check_silo_first_start() → settings prompt
└─ 4000ms: _setup_silo_activity_panel() → "Database Activity" dock
```

View File

@@ -62,12 +62,12 @@ These appear in the File menu and "Origin Tools" toolbar across all workbenches
| `Silo_Rollback` | Rollback to previous revision |
| `Silo_SetStatus` | Set revision status (draft/review/released/obsolete) |
| `Silo_Settings` | Configure API URL, projects dir, SSL certificates |
| `Silo_ToggleMode` | Swap Ctrl+O/S/N between FreeCAD and Silo commands |
| `Silo_Auth` | Login/logout authentication panel |
**Global integration** via `SiloMenuManipulator` in `src/Mod/Create/InitGui.py`:
- File menu: Silo_New, Silo_Open, Silo_Save, Silo_Commit, Silo_Pull, Silo_Push, Silo_BOM
- File toolbar: Silo_ToggleMode button
**Global integration** via the origin system in `src/Gui/`:
- File toolbar: `Std_Origin` selector widget + `Std_New`/`Std_Open`/`Std_Save` (delegate to current origin)
- Origin Tools toolbar: `Origin_Commit`/`Origin_Pull`/`Origin_Push`/`Origin_Info`/`Origin_BOM` (auto-disable by capability)
- Silo origin registered at startup by `src/Mod/Create/InitGui.py`
**Server architecture:** Go REST API (38+ routes) + PostgreSQL + MinIO S3. Authentication via local (bcrypt), LDAP, or OIDC backends. See `mods/silo/docs/` for server documentation.

View File

@@ -58,11 +58,11 @@ The Python API provides everything needed for feature creation, command registra
The Create module is a thin Python loader that:
- Adds `mods/` addon paths to `sys.path` and executes their `Init.py`/`InitGui.py` files
- Installs `SiloMenuManipulator` for global File menu/toolbar injection
- Registers the Silo FileOrigin so the origin selector can offer it at startup
- Sets up deferred Silo dock panels (auth, activity) via `QTimer`
- Handles first-start configuration
This layer does not contain C++ code. It uses FreeCAD's `WorkbenchManipulator` API for menu/toolbar injection.
This layer does not contain C++ code.
### Layer 4: Kindred Workbenches -- `mods/`
@@ -116,9 +116,9 @@ Pure Python workbenches following FreeCAD's addon pattern. Self-contained with `
**Goal:** Silo commands available globally, not just in the Silo workbench.
**Implementation:** `SiloMenuManipulator` in `src/Mod/Create/InitGui.py` uses `FreeCADGui.addWorkbenchManipulator()` to inject Silo commands into the File menu and toolbar across all workbenches. `Silo_ToggleMode` provides a one-click swap of Ctrl+O/S/N between standard FreeCAD and Silo file commands.
**Implementation:** The unified origin system (`FileOrigin`, `OriginManager`, `OriginSelectorWidget`) in `src/Gui/` delegates all file operations (New/Open/Save) to the selected origin. Standard commands (`Std_New`, `Std_Open`, `Std_Save`) and origin commands (`Origin_Commit`, `Origin_Pull`, `Origin_Push`, `Origin_Info`, `Origin_BOM`) are built into the File toolbar and menu. The Silo workbench no longer has its own toolbar — it only provides a menu with admin/management commands.
**Dock panels:** Database Auth (1500ms) and Database Activity (4000ms) panels are created via deferred QTimers and docked in the right panel area.
**Dock panels:** Database Auth (2000ms) and Database Activity (4000ms) panels are created via deferred QTimers and docked in the right panel area.
### Phase 6: Build system integration -- PARTIAL

View File

@@ -8,7 +8,7 @@
2. **WorkbenchManipulator timing.** The `_ZToolsPartDesignManipulator` appends commands by name. If ZToolsWorkbench hasn't been activated when the user switches to PartDesign, the commands may not be registered. The manipulator API tolerates missing commands silently, but buttons won't appear.
3. **Silo shortcut persistence.** `Silo_ToggleMode` stores original shortcuts in a module-level dict. If FreeCAD crashes with Silo mode on, original shortcuts are lost on next launch.
3. ~~**Silo shortcut persistence.**~~ Resolved. `Silo_ToggleMode` removed; file operations now delegate to the selected origin via the unified origin system.
### High

View File

@@ -1,8 +1,117 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<path d="M8 6 L20 6 L24 10 L24 26 L8 26 Z" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<path d="M20 6 L20 10 L24 10" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<line x1="11" y1="14" x2="21" y2="14" stroke="#74c7ec" stroke-width="1.5"/>
<line x1="11" y1="18" x2="21" y2="18" stroke="#74c7ec" stroke-width="1.5"/>
<line x1="11" y1="22" x2="17" y2="22" stroke="#74c7ec" stroke-width="1.5"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg4"
sodipodi:docname="Document.svg"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect5"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.8970359,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect4"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,2.25625,0,1 @ F,0,0,1,0,1.411235,0,1 @ F,0,0,1,0,1.2732549,0,1 @ F,0,0,1,0,2.25625,0,1 @ F,0,0,1,0,1.875,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="32"
inkscape:cx="10.90625"
inkscape:cy="12.21875"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<path
d="m 10.246497,6 h 8.332515 a 3.4070227,3.4070227 22.5 0 1 2.409129,0.9978938 l 2.101779,2.101779 a 3.0739092,3.0739092 67.5 0 1 0.900327,2.1735822 l 0,12.470495 A 2.25625,2.25625 135 0 1 21.733997,26 H 9.8652468 a 1.875,1.875 45 0 1 -1.875,-1.875 V 8.25625 A 2.25625,2.25625 135 0 1 10.246497,6 Z"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="path1"
inkscape:path-effect="#path-effect4"
inkscape:original-d="M 7.9902468,6 H 19.990247 l 4,4 V 26 H 7.9902468 Z" />
<path
d="m 19.707388,5.8623613 v 3.4443418 a 0.8970359,0.8970359 45 0 0 0.897036,0.8970359 h 3.385823"
fill="none"
stroke="#74c7ec"
stroke-width="1.61701"
id="path2"
style="stroke:#89b4fa;stroke-width:1.5;stroke-dasharray:none;stroke-opacity:1"
inkscape:path-effect="#path-effect5"
inkscape:original-d="m 19.707388,5.8623613 v 4.3413777 h 4.282859" />
<line
x1="11"
y1="14"
x2="21"
y2="14"
stroke="#74c7ec"
stroke-width="1.5"
id="line2"
style="stroke-linecap:round" />
<line
x1="11"
y1="18"
x2="21"
y2="18"
stroke="#74c7ec"
stroke-width="1.5"
id="line3"
style="stroke-linecap:round" />
<line
x1="11"
y1="22"
x2="17"
y2="22"
stroke="#74c7ec"
stroke-width="1.5"
id="line4"
style="stroke-linecap:round" />
</svg>

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<ellipse cx="16" cy="16" rx="10" ry="6" fill="none" stroke="#f9e2af" stroke-width="2"/>
<circle cx="16" cy="16" r="3" fill="#fab387"/>
<ellipse cx="16" cy="16" rx="10" ry="6" fill="none" stroke="#cdd6f4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="16" cy="16" r="3" fill="none" stroke="#cdd6f4" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<path d="M8 12 L16 8 L24 12 L24 22 L16 26 L8 22 Z" fill="#f9e2af" fill-opacity="0.6" stroke="#fab387" stroke-width="1.5"/>
<path d="M8 12 L16 16 L24 12 M16 16 L16 26" fill="none" stroke="#fab387" stroke-width="1.5"/>
<path d="M5.5 9.5 16 4l10.5 5.5v13L16 28 5.5 22.5z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 9.5 16 15l10.5-5.5M16 15v13" fill="none" stroke="#89b4fa" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<path d="M8 12 L16 8 L24 12 L24 22 L16 26 L8 22 Z" fill="none" stroke="#f9e2af" stroke-width="1.5"/>
<path d="M8 12 L16 16 L24 12 M16 16 L16 26" fill="none" stroke="#fab387" stroke-width="1.5" stroke-dasharray="2,2"/>
</svg>

Before

Width:  |  Height:  |  Size: 345 B

View File

@@ -1,7 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<rect x="8" y="10" width="16" height="12" fill="none" stroke="#f9e2af" stroke-width="2"/>
<line x1="8" y1="10" x2="12" y2="6" stroke="#fab387" stroke-width="1.5"/>
<line x1="24" y1="10" x2="28" y2="6" stroke="#fab387" stroke-width="1.5"/>
<line x1="12" y1="6" x2="28" y2="6" stroke="#fab387" stroke-width="1.5"/>
<path d="M5.5 9.5 16 4l10.5 5.5v13L16 28 5.5 22.5z" fill="#585b70" stroke="#cdd6f4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 9.5 16 15l10.5-5.5M16 15v13" fill="none" stroke="#cdd6f4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 444 B

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<circle cx="8" cy="12" r="2" fill="#f9e2af"/>
<circle cx="16" cy="8" r="2" fill="#f9e2af"/>
<circle cx="24" cy="12" r="2" fill="#f9e2af"/>
<circle cx="8" cy="22" r="2" fill="#fab387"/>
<circle cx="16" cy="26" r="2" fill="#fab387"/>
<circle cx="24" cy="22" r="2" fill="#fab387"/>
<circle cx="16" cy="16" r="2" fill="#f9e2af"/>
<circle cx="16" cy="4" r="1.5" fill="#cdd6f4"/>
<circle cx="5.5" cy="9.5" r="1.5" fill="#cdd6f4"/>
<circle cx="26.5" cy="9.5" r="1.5" fill="#cdd6f4"/>
<circle cx="16" cy="15" r="1.5" fill="#cdd6f4"/>
<circle cx="5.5" cy="22.5" r="1.5" fill="#cdd6f4"/>
<circle cx="26.5" cy="22.5" r="1.5" fill="#cdd6f4"/>
<circle cx="16" cy="28" r="1.5" fill="#cdd6f4"/>
</svg>

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<path d="M8 12 L16 8 L24 12 L24 22 L16 26 L8 22 Z" fill="#f9e2af" stroke="#fab387" stroke-width="1.5"/>
<path d="M8 12 L16 16 L24 12 M16 16 L16 26" fill="none" stroke="#313244" stroke-width="1"/>
<path d="M5.5 9.5 16 4l10.5 5.5v13L16 28 5.5 22.5z" fill="#45475a" stroke="#cdd6f4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 9.5 16 15l10.5-5.5M16 15v13" fill="none" stroke="#cdd6f4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 323 B

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<path d="M8 12 L16 8 L24 12 L24 22 L16 26 L8 22 Z" fill="none" stroke="#f9e2af" stroke-width="1.5"/>
<path d="M8 12 L16 16 L24 12 M16 16 L16 26" fill="none" stroke="#fab387" stroke-width="1.5"/>
<path d="M5.5 9.5 16 4l10.5 5.5v13L16 28 5.5 22.5z" fill="none" stroke="#cdd6f4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 9.5 16 15l10.5-5.5M16 15v13" fill="none" stroke="#cdd6f4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 322 B

After

Width:  |  Height:  |  Size: 416 B

View File

@@ -1,9 +1,62 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<circle cx="10" cy="10" r="4" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<circle cx="22" cy="10" r="4" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<circle cx="10" cy="22" r="4" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<circle cx="22" cy="22" r="4" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<line x1="14" y1="10" x2="18" y2="10" stroke="#89b4fa" stroke-width="1.5"/>
<line x1="10" y1="14" x2="10" y2="18" stroke="#74c7ec" stroke-width="1.5"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<circle
cx="10"
cy="10"
r="4"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle1" />
<circle
cx="22"
cy="10"
r="4"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle2" />
<circle
cx="10"
cy="22"
r="4"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle3" />
<circle
cx="22"
cy="22"
r="4"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle4" />
<line
x1="14"
y1="10"
x2="18"
y2="10"
stroke="#cba6f7"
stroke-width="1.5"
id="line1" />
<line
x1="10"
y1="14"
x2="10"
y2="18"
stroke="#cba6f7"
stroke-width="1.5"
id="line2" />
</svg>

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,7 +1,52 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<rect x="6" y="6" width="10" height="10" rx="1" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<rect x="10" y="10" width="10" height="10" rx="1" fill="#313244" stroke="#74c7ec" stroke-width="1.5"/>
<rect x="14" y="14" width="10" height="10" rx="1" fill="#313244" stroke="#89b4fa" stroke-width="1.5"/>
<circle cx="24" cy="8" r="4" fill="none" stroke="#a6e3a1" stroke-width="1.5"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<rect
x="6"
y="6"
width="10"
height="10"
rx="1.5"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="rect2" />
<rect
x="10"
y="10"
width="10"
height="10"
rx="1.5"
fill="#313244"
stroke="#cba6f7"
stroke-width="1.5"
id="rect3" />
<rect
x="14"
y="14"
width="10"
height="10"
rx="1.5"
fill="#313244"
stroke="#89b4fa"
stroke-width="1.5"
id="rect4" />
<circle
cx="24"
cy="8"
r="4"
fill="none"
stroke="#a6e3a1"
stroke-width="1.5"
id="circle1" />
</svg>

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 910 B

View File

@@ -1,6 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<rect x="8" y="8" width="10" height="10" rx="1" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<path d="M18 18 L24 24" stroke="#74c7ec" stroke-width="2"/>
<circle cx="24" cy="24" r="3" fill="#74c7ec"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<rect
x="8"
y="8"
width="10"
height="10"
rx="1.5"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="rect2" />
<path
d="M 18,18 24,24"
stroke="#89b4fa"
stroke-width="1.5"
id="path1" />
<circle
cx="24"
cy="24"
r="3"
fill="#cba6f7"
id="circle1" />
</svg>

Before

Width:  |  Height:  |  Size: 334 B

After

Width:  |  Height:  |  Size: 636 B

View File

@@ -1,7 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<rect x="6" y="10" width="20" height="14" rx="2" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<circle cx="12" cy="17" r="3" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<circle cx="20" cy="17" r="3" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<line x1="15" y1="17" x2="17" y2="17" stroke="#89b4fa" stroke-width="1.5"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<rect
x="6"
y="10"
width="20"
height="14"
rx="2"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="rect2" />
<circle
cx="12"
cy="17"
r="3"
fill="none"
stroke="#cba6f7"
stroke-width="1.5"
id="circle1" />
<circle
cx="20"
cy="17"
r="3"
fill="none"
stroke="#cba6f7"
stroke-width="1.5"
id="circle2" />
<line
x1="15"
y1="17"
x2="17"
y2="17"
stroke="#89b4fa"
stroke-width="1.5"
id="line1" />
</svg>

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 838 B

View File

@@ -1,7 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<circle cx="10" cy="10" r="3" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<circle cx="22" cy="10" r="3" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<circle cx="10" cy="20" r="3" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<path d="M24 18 L24 26 L18 22 Z" fill="#a6e3a1"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<circle
cx="10"
cy="10"
r="3"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle1" />
<circle
cx="22"
cy="10"
r="3"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle2" />
<circle
cx="10"
cy="20"
r="3"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle3" />
<path
d="M 24,18 24,26 18,22 Z"
fill="#a6e3a1"
id="path1" />
</svg>

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 764 B

View File

@@ -1,5 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<rect x="6" y="6" width="12" height="12" rx="1" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<rect x="14" y="14" width="12" height="12" rx="1" fill="#313244" stroke="#74c7ec" stroke-width="1.5"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<rect
x="6"
y="6"
width="12"
height="12"
rx="1.5"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="rect2" />
<rect
x="14"
y="14"
width="12"
height="12"
rx="1.5"
fill="#313244"
stroke="#cba6f7"
stroke-width="1.5"
id="rect3" />
</svg>

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 616 B

View File

@@ -1,7 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<circle cx="10" cy="16" r="5" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
<circle cx="22" cy="16" r="5" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
<path d="M13 12 L19 12 M19 12 L17 10 M19 12 L17 14" stroke="#a6e3a1" stroke-width="1.5"/>
<path d="M19 20 L13 20 M13 20 L15 18 M13 20 L15 22" stroke="#f38ba8" stroke-width="1.5"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<circle
cx="10"
cy="16"
r="5"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle1" />
<circle
cx="22"
cy="16"
r="5"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="circle2" />
<path
d="M 13,12 19,12 M 19,12 17,10 M 19,12 17,14"
stroke="#a6e3a1"
stroke-width="1.5"
fill="none"
id="path1" />
<path
d="M 19,20 13,20 M 13,20 15,18 M 13,20 15,22"
stroke="#f38ba8"
stroke-width="1.5"
fill="none"
id="path2" />
</svg>

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 837 B

View File

@@ -1,14 +1,62 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg5"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
x="2"
y="2"
width="28"
height="28"
rx="4"
fill="#313244"
id="rect1" />
<!-- Base sketch profile -->
<path d="M6 24 L6 20 L14 18 L26 20 L26 24 L14 26 Z" fill="#45475a" stroke="#6c7086" stroke-width="1"/>
<path
d="M 6,24 6,20 14,18 26,20 26,24 14,26 Z"
fill="#45475a"
stroke="#6c7086"
stroke-width="1"
id="path1" />
<!-- Extruded body -->
<path d="M6 20 L6 10 L14 8 L26 10 L26 20 L14 22 Z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
<path d="M6 10 L14 12 L26 10" stroke="#89b4fa" stroke-width="1.5" fill="none"/>
<path d="M14 12 L14 22" stroke="#89b4fa" stroke-width="1.5"/>
<path
d="M 6,20 6,10 14,8 26,10 26,20 14,22 Z"
fill="#45475a"
stroke="#89b4fa"
stroke-width="1.5"
id="path2" />
<path
d="M 6,10 14,12 26,10"
stroke="#89b4fa"
stroke-width="1.5"
fill="none"
id="path3" />
<path
d="M 14,12 V 22"
stroke="#89b4fa"
stroke-width="1.5"
id="path4" />
<!-- Top face -->
<path d="M6 10 L14 8 L26 10 L14 12 Z" fill="#74c7ec" fill-opacity="0.4"/>
<path
d="M 6,10 14,8 26,10 14,12 Z"
fill="#74c7ec"
fill-opacity="0.4"
id="path5" />
<!-- Extrude arrow -->
<path d="M20 18 L20 6" stroke="#a6e3a1" stroke-width="2" stroke-linecap="round"/>
<path d="M17 9 L20 5 L23 9" stroke="#a6e3a1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<path
d="M 20,18 V 6"
stroke="#a6e3a1"
stroke-width="2"
stroke-linecap="round"
id="path6" />
<path
d="M 17,9 20,5 23,9"
stroke="#a6e3a1"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
id="path7" />
</svg>

Before

Width:  |  Height:  |  Size: 878 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,14 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg5"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
x="2"
y="2"
width="28"
height="28"
rx="4"
fill="#313244"
id="rect1" />
<!-- Solid block -->
<path d="M4 22 L4 10 L16 6 L28 10 L28 22 L16 26 Z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
<path d="M4 10 L16 14 L28 10" stroke="#89b4fa" stroke-width="1.5" fill="none"/>
<path d="M16 14 L16 26" stroke="#89b4fa" stroke-width="1.5"/>
<path
d="M 4,22 4,10 16,6 28,10 28,22 16,26 Z"
fill="#45475a"
stroke="#89b4fa"
stroke-width="1.5"
id="path1" />
<path
d="M 4,10 16,14 28,10"
stroke="#89b4fa"
stroke-width="1.5"
fill="none"
id="path2" />
<path
d="M 16,14 V 26"
stroke="#89b4fa"
stroke-width="1.5"
id="path3" />
<!-- Pocket cut-out -->
<path d="M10 12 L16 10 L22 12 L22 18 L16 20 L10 18 Z" fill="#1e1e2e" stroke="#f38ba8" stroke-width="1.5"/>
<path d="M10 12 L16 14 L22 12" stroke="#f38ba8" stroke-width="1" fill="none"/>
<path d="M16 14 L16 20" stroke="#f38ba8" stroke-width="1"/>
<path
d="M 10,12 16,10 22,12 22,18 16,20 10,18 Z"
fill="#1e1e2e"
stroke="#f38ba8"
stroke-width="1.5"
id="path4" />
<path
d="M 10,12 16,14 22,12"
stroke="#f38ba8"
stroke-width="1"
fill="none"
id="path5" />
<path
d="M 16,14 V 20"
stroke="#f38ba8"
stroke-width="1"
id="path6" />
<!-- Cut arrow -->
<path d="M16 8 L16 16" stroke="#f38ba8" stroke-width="1.5" stroke-linecap="round"/>
<path d="M14 13 L16 17 L18 13" stroke="#f38ba8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<path
d="M 16,8 V 16"
stroke="#f38ba8"
stroke-width="1.5"
stroke-linecap="round"
id="path7" />
<path
d="M 14,13 16,17 18,13"
stroke="#f38ba8"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
id="path8" />
</svg>

Before

Width:  |  Height:  |  Size: 925 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,6 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#313244"/>
<path d="M12 20 L8 20 A4 4 0 0 1 8 12 L12 12" fill="none" stroke="#585b70" stroke-width="2"/>
<path d="M20 12 L24 12 A4 4 0 0 1 24 20 L20 20" fill="none" stroke="#585b70" stroke-width="2"/>
<line x1="6" y1="26" x2="26" y2="6" stroke="#f38ba8" stroke-width="2"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
width="32"
height="32"
rx="4"
fill="#313244"
id="rect1" />
<path
d="M 12,20 H 8 A 4,4 0 0 1 8,12 h 4"
fill="none"
stroke="#585b70"
stroke-width="1.5"
id="path1" />
<path
d="M 20,12 h 4 A 4,4 0 0 1 24,20 h -4"
fill="none"
stroke="#585b70"
stroke-width="1.5"
id="path2" />
<line
x1="6"
y1="26"
x2="26"
y2="6"
stroke="#f38ba8"
stroke-width="2"
id="line1" />
</svg>

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 680 B

View File

@@ -1,10 +1,60 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
<!-- Document shape -->
<path d="M9 5 L9 27 L23 27 L23 11 L17 5 Z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
<!-- Folded corner -->
<path d="M17 5 L17 11 L23 11" fill="#313244" stroke="#89b4fa" stroke-width="1.5" stroke-linejoin="round"/>
<!-- Plus sign for "new" -->
<circle cx="22" cy="22" r="6" fill="#a6e3a1"/>
<path d="M22 19 L22 25 M19 22 L25 22" stroke="#1e1e2e" stroke-width="2" stroke-linecap="round"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg4"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
x="2"
y="2"
width="28"
height="28"
rx="4"
fill="#313244"
id="rect1" />
<path
d="M 10,6 H 18 L 24,12 V 26 H 10 Z"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="path1"
style="stroke-linejoin:round" />
<path
d="M 18,6 V 12 H 24"
fill="none"
stroke="#89b4fa"
stroke-width="1.5"
id="path2"
style="stroke-linejoin:round" />
<line
x1="12"
y1="16"
x2="20"
y2="16"
stroke="#74c7ec"
stroke-width="1.5"
id="line1"
style="stroke-linecap:round" />
<line
x1="12"
y1="20"
x2="20"
y2="20"
stroke="#74c7ec"
stroke-width="1.5"
id="line2"
style="stroke-linecap:round" />
<circle
cx="24"
cy="24"
r="5"
fill="#a6e3a1"
id="circle1" />
<path
d="M 24,21.5 V 26.5 M 21.5,24 H 26.5"
stroke="#1e1e2e"
stroke-width="1.5"
stroke-linecap="round"
id="path3" />
</svg>

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,14 +1,75 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg4"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
x="2"
y="2"
width="28"
height="28"
rx="4"
fill="#313244"
id="rect1" />
<!-- Printer body -->
<rect x="5" y="12" width="22" height="10" rx="2" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
<rect
x="5"
y="12"
width="22"
height="10"
rx="2"
fill="#45475a"
stroke="#89b4fa"
stroke-width="1.5"
id="rect2" />
<!-- Paper input (top) -->
<rect x="9" y="6" width="14" height="8" rx="1" fill="#cdd6f4" stroke="#6c7086" stroke-width="1"/>
<rect
x="9"
y="6"
width="14"
height="8"
rx="1"
fill="#cdd6f4"
stroke="#6c7086"
stroke-width="1"
id="rect3" />
<!-- Paper output (bottom) -->
<rect x="9" y="20" width="14" height="8" rx="1" fill="#cdd6f4" stroke="#6c7086" stroke-width="1"/>
<rect
x="9"
y="20"
width="14"
height="8"
rx="1"
fill="#cdd6f4"
stroke="#6c7086"
stroke-width="1"
id="rect4" />
<!-- Paper lines -->
<line x1="11" y1="23" x2="21" y2="23" stroke="#585b70" stroke-width="1" stroke-linecap="round"/>
<line x1="11" y1="25" x2="18" y2="25" stroke="#585b70" stroke-width="1" stroke-linecap="round"/>
<line
x1="11"
y1="23"
x2="21"
y2="23"
stroke="#585b70"
stroke-width="1"
stroke-linecap="round"
id="line1" />
<line
x1="11"
y1="25.5"
x2="18"
y2="25.5"
stroke="#585b70"
stroke-width="1"
stroke-linecap="round"
id="line2" />
<!-- Printer status light -->
<circle cx="23" cy="15" r="1.5" fill="#a6e3a1"/>
<circle
cx="23"
cy="15"
r="1.5"
fill="#a6e3a1"
id="circle1" />
</svg>

Before

Width:  |  Height:  |  Size: 830 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,15 +1,63 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg4"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
x="2"
y="2"
width="28"
height="28"
rx="4"
fill="#313244"
id="rect1" />
<!-- Floppy disk body -->
<path d="M7 6 L7 26 L25 26 L25 10 L21 6 Z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
<path
d="M 7,6 H 21 L 25,10 V 26 H 7 Z"
fill="#45475a"
stroke="#89b4fa"
stroke-width="1.5"
id="path1"
style="stroke-linejoin:round" />
<!-- Metal slider -->
<rect x="10" y="6" width="10" height="8" rx="1" fill="#1e1e2e" stroke="#6c7086" stroke-width="1"/>
<rect
x="10"
y="6"
width="10"
height="8"
rx="1"
fill="#1e1e2e"
stroke="#6c7086"
stroke-width="1"
id="rect2" />
<!-- Slider notch -->
<rect x="17" y="7" width="2" height="6" fill="#313244"/>
<rect
x="17"
y="7"
width="2"
height="6"
fill="#313244"
id="rect3" />
<!-- Label area -->
<rect x="9" y="17" width="14" height="7" rx="1" fill="#cdd6f4"/>
<!-- Pencil icon for "save as" -->
<g transform="translate(18, 14)">
<path d="M6 2 L8 0 L10 2 L4 8 L2 8 L2 6 Z" fill="#f9e2af" stroke="#fab387" stroke-width="0.5"/>
</g>
<rect
x="9"
y="17"
width="14"
height="7"
rx="1"
fill="#cdd6f4"
id="rect4" />
<!-- Pencil badge for "save as" -->
<circle
cx="24"
cy="24"
r="5"
fill="#f9e2af"
id="circle1" />
<path
d="M 22,26 22,24 26,20 28,22 24,26 Z"
fill="#1e1e2e"
id="path2" />
</svg>

Before

Width:  |  Height:  |  Size: 738 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,14 +1,71 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg4"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<rect
x="2"
y="2"
width="28"
height="28"
rx="4"
fill="#313244"
id="rect1" />
<!-- Floppy disk body -->
<path d="M7 6 L7 26 L25 26 L25 10 L21 6 Z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
<path
d="M 7,6 H 21 L 25,10 V 26 H 7 Z"
fill="#45475a"
stroke="#89b4fa"
stroke-width="1.5"
id="path1"
style="stroke-linejoin:round" />
<!-- Metal slider -->
<rect x="10" y="6" width="10" height="8" rx="1" fill="#1e1e2e" stroke="#6c7086" stroke-width="1"/>
<rect
x="10"
y="6"
width="10"
height="8"
rx="1"
fill="#1e1e2e"
stroke="#6c7086"
stroke-width="1"
id="rect2" />
<!-- Slider notch -->
<rect x="17" y="7" width="2" height="6" fill="#313244"/>
<rect
x="17"
y="7"
width="2"
height="6"
fill="#313244"
id="rect3" />
<!-- Label area -->
<rect x="9" y="17" width="14" height="7" rx="1" fill="#cdd6f4"/>
<rect
x="9"
y="17"
width="14"
height="7"
rx="1"
fill="#cdd6f4"
id="rect4" />
<!-- Label lines -->
<line x1="11" y1="19" x2="21" y2="19" stroke="#313244" stroke-width="1" stroke-linecap="round"/>
<line x1="11" y1="22" x2="17" y2="22" stroke="#313244" stroke-width="1" stroke-linecap="round"/>
<line
x1="11"
y1="19.5"
x2="21"
y2="19.5"
stroke="#313244"
stroke-width="1"
stroke-linecap="round"
id="line1" />
<line
x1="11"
y1="22"
x2="17"
y2="22"
stroke="#313244"
stroke-width="1"
stroke-linecap="round"
id="line2" />
</svg>

Before

Width:  |  Height:  |  Size: 779 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -112,6 +112,10 @@ export XKB_CONFIG_ROOT="${KINDRED_CREATE_HOME}/share/X11/xkb"
export FONTCONFIG_FILE="${KINDRED_CREATE_HOME}/etc/fonts/fonts.conf"
export FONTCONFIG_PATH="${KINDRED_CREATE_HOME}/etc/fonts"
# Qt Wayland fractional scaling — force integer rounding to avoid blurry text
export QT_SCALE_FACTOR_ROUNDING_POLICY=RoundPreferFloor
export QT_ENABLE_HIGHDPI_SCALING=1
# Use system CA certificates so bundled Python trusts internal CAs (e.g. FreeIPA)
# The bundled openssl has a hardcoded cafile from the build environment which
# does not exist on the target system.

View File

@@ -9,8 +9,9 @@ export PATH_TO_FREECAD_LIBDIR=${HERE}/usr/lib
export FONTCONFIG_FILE=/etc/fonts/fonts.conf
export FONTCONFIG_PATH=/etc/fonts
# Fix: Use X to run on Wayland
export QT_QPA_PLATFORM=xcb
# Qt HiDPI scaling — force integer rounding to avoid blurry text on fractional scales
export QT_SCALE_FACTOR_ROUNDING_POLICY=RoundPreferFloor
export QT_ENABLE_HIGHDPI_SCALING=1
# Show packages info if DEBUG env variable is set
if [ "$DEBUG" = 1 ]; then

View File

@@ -72,9 +72,26 @@
<FCUInt Name="BacklightColor" Value="1162304255"/>
<FCFloat Name="BacklightIntensity" Value="0.30"/>
</FCParamGroup>
<FCParamGroup Name="Document">
<FCInt Name="MaxUndoSize" Value="50"/>
<FCInt Name="AutoSaveTimeout" Value="5"/>
<FCInt Name="CountBackupFiles" Value="3"/>
<FCInt Name="prefLicenseType" Value="19"/>
<FCText Name="prefLicenseUrl"></FCText>
</FCParamGroup>
<FCParamGroup Name="TreeView">
<FCUInt Name="TreeEditColor" Value="3416717311"/>
<FCUInt Name="TreeActiveColor" Value="2799935999"/>
<FCBool Name="PreSelection" Value="1"/>
<FCBool Name="SyncView" Value="1"/>
<FCBool Name="SyncSelection" Value="1"/>
</FCParamGroup>
<FCParamGroup Name="NotificationArea">
<FCInt Name="MaxWidgetMessages" Value="100"/>
<FCInt Name="MaxOpenNotifications" Value="3"/>
<FCInt Name="NotificiationWidth" Value="400"/>
<FCInt Name="NotificationTime" Value="10"/>
<FCInt Name="MinimumOnScreenTime" Value="3"/>
</FCParamGroup>
<FCParamGroup Name="General">
<FCText Name="AutoloadModule">ZToolsWorkbench</FCText>

View File

@@ -31,6 +31,8 @@
#include "OriginManager.h"
#include "FileOrigin.h"
#include "BitmapFactory.h"
#include "Application.h"
#include "Command.h"
namespace Gui {
@@ -176,10 +178,7 @@ FileOrigin* OriginManagerDialog::selectedOrigin() const
void OriginManagerDialog::onAddSilo()
{
// TODO: Open SiloConfigDialog for adding new instance
QMessageBox::information(this, tr("Add Silo"),
tr("Silo configuration dialog not yet implemented.\n\n"
"To add a Silo instance, configure it in the Silo workbench preferences."));
Application::Instance->commandManager().runCommandByName("Silo_Settings");
}
void OriginManagerDialog::onEditOrigin()
@@ -189,10 +188,7 @@ void OriginManagerDialog::onEditOrigin()
return;
}
// TODO: Open SiloConfigDialog for editing
QMessageBox::information(this, tr("Edit Origin"),
tr("Origin editing not yet implemented.\n\n"
"To edit this origin, modify settings in the Silo workbench preferences."));
Application::Instance->commandManager().runCommandByName("Silo_Settings");
}
void OriginManagerDialog::onRemoveOrigin()

View File

@@ -54,8 +54,9 @@ void OriginSelectorWidget::setupUi()
{
setPopupMode(QToolButton::InstantPopup);
setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
setMinimumWidth(70);
setMaximumWidth(120);
setMinimumWidth(80);
setMaximumWidth(160);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
// Create menu
m_menu = new QMenu(this);

View File

@@ -28,6 +28,7 @@
#include <QApplication>
#include <QFileDialog>
#include <QLocale>
#include <QMenu>
#include <QMessageBox>
#include <QString>
#include <algorithm>
@@ -96,7 +97,6 @@ DlgSettingsGeneral::DlgSettingsGeneral(QWidget* parent)
ui->SaveNewPreferencePack->setEnabled(false);
ui->ManagePreferencePacks->setEnabled(false);
ui->themesCombobox->setEnabled(false);
ui->moreThemesLabel->setEnabled(false);
}
}
}
@@ -121,7 +121,6 @@ DlgSettingsGeneral::DlgSettingsGeneral(QWidget* parent)
this,
&DlgSettingsGeneral::onThemeChanged
);
connect(ui->moreThemesLabel, &QLabel::linkActivated, this, &DlgSettingsGeneral::onLinkActivated);
}
// If there are any saved config file backs, show the revert button, otherwise hide it:
@@ -147,9 +146,6 @@ DlgSettingsGeneral::DlgSettingsGeneral(QWidget* parent)
const auto visible = UnitsApi::isMultiUnitLength();
ui->comboBox_FracInch->setVisible(visible);
ui->fractionalInchLabel->setVisible(visible);
ui->moreThemesLabel->setEnabled(
Application::Instance->commandManager().getCommandByName("Std_AddonMgr") != nullptr
);
}
/**
@@ -166,7 +162,7 @@ void DlgSettingsGeneral::setRecentFileSize()
auto recent = getMainWindow()->findChild<RecentFilesAction*>(QLatin1String("recentFiles"));
if (recent) {
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("RecentFiles");
recent->resizeList(hGrp->GetInt("RecentFiles", 4));
recent->resizeList(hGrp->GetInt("RecentFiles", 10));
}
}
@@ -264,6 +260,11 @@ void DlgSettingsGeneral::saveSettings()
hGrp->SetInt("ToolbarIconSize", pixel);
getMainWindow()->setIconSize(QSize(pixel, pixel));
QVariant menuSize = ui->menuIconSize->itemData(ui->menuIconSize->currentIndex());
int menuPixel = menuSize.toInt();
hGrp->SetInt("MenuIconSize", menuPixel);
applyMenuIconSize(menuPixel);
int blinkTime {hGrp->GetBool("EnableCursorBlinking", true) ? -1 : 0};
qApp->setCursorFlashTime(blinkTime);
@@ -348,6 +349,7 @@ void DlgSettingsGeneral::loadSettings()
}
addIconSizes(getCurrentIconSize());
addMenuIconSizes(getCurrentMenuIconSize());
// TreeMode combobox setup.
loadDockWindowVisibility();
@@ -395,8 +397,9 @@ void DlgSettingsGeneral::resetSettingsToDefaults()
hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
// reset "Language" parameter
hGrp->RemoveASCII("Language");
// reset "ToolbarIconSize" parameter
// reset "ToolbarIconSize" and "MenuIconSize" parameters
hGrp->RemoveInt("ToolbarIconSize");
hGrp->RemoveInt("MenuIconSize");
// finally reset all the parameters associated to Gui::Pref* widgets
PreferencePage::resetSettingsToDefaults();
@@ -534,6 +537,63 @@ void DlgSettingsGeneral::translateIconSizes()
}
}
int DlgSettingsGeneral::getCurrentMenuIconSize() const
{
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
return hGrp->GetInt("MenuIconSize", 24);
}
void DlgSettingsGeneral::addMenuIconSizes(int current)
{
ui->menuIconSize->clear();
QList<int> sizes {16, 20, 24, 28};
if (!sizes.contains(current)) {
sizes.append(current);
}
for (int size : sizes) {
ui->menuIconSize->addItem(QString(), QVariant(size));
}
int index = ui->menuIconSize->findData(QVariant(current));
ui->menuIconSize->setCurrentIndex(index);
translateMenuIconSizes();
}
void DlgSettingsGeneral::translateMenuIconSizes()
{
auto getSize = [this](int index) {
return ui->menuIconSize->itemData(index).toInt();
};
QStringList sizes;
sizes << tr("Small (%1px)").arg(getSize(0));
sizes << tr("Medium (%1px)").arg(getSize(1));
sizes << tr("Large (%1px)").arg(getSize(2));
sizes << tr("Extra large (%1px)").arg(getSize(3));
if (ui->menuIconSize->count() > 4) {
sizes << tr("Custom (%1px)").arg(getSize(4));
}
for (int index = 0; index < sizes.size(); index++) {
ui->menuIconSize->setItemText(index, sizes[index]);
}
}
void DlgSettingsGeneral::applyMenuIconSize(int pixel)
{
// Apply menu icon size via stylesheet override on all QMenu widgets
QString rule = QStringLiteral("QMenu::icon { width: %1px; height: %1px; }").arg(pixel);
for (auto* widget : qApp->allWidgets()) {
if (auto* menu = qobject_cast<QMenu*>(widget)) {
menu->setStyleSheet(rule);
}
}
// Store the rule so new menus pick it up via the main window
getMainWindow()->setProperty("_menuIconSizeRule", rule);
}
void DlgSettingsGeneral::retranslateUnits()
{
auto setItem = [&, index {0}](const std::string& item) mutable {
@@ -547,6 +607,7 @@ void DlgSettingsGeneral::changeEvent(QEvent* event)
{
if (event->type() == QEvent::LanguageChange) {
translateIconSizes();
translateMenuIconSizes();
retranslateUnits();
int index = ui->UseLocaleFormatting->currentIndex();
ui->retranslateUi(this);
@@ -823,24 +884,6 @@ void DlgSettingsGeneral::onThemeChanged(int index)
themeChanged = true;
}
void DlgSettingsGeneral::onLinkActivated(const QString& link)
{
auto const addonManagerLink = QStringLiteral("freecad:Std_AddonMgr");
if (link != addonManagerLink) {
return;
}
// Set the user preferences to include only preference packs.
// This is a quick and dirty way to open Addon Manager with only themes.
auto pref = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Addons"
);
pref->SetInt("PackageTypeSelection", 3); // 3 stands for Preference Packs
pref->SetInt("StatusSelection", 0); // 0 stands for any installation status
Gui::Application::Instance->commandManager().runCommandByName("Std_AddonMgr");
}
///////////////////////////////////////////////////////////
namespace

View File

@@ -72,7 +72,6 @@ protected Q_SLOTS:
void onManagePreferencePacksClicked();
void onImportConfigClicked();
void onThemeChanged(int index);
void onLinkActivated(const QString& link);
public Q_SLOTS:
void onUnitSystemIndexChanged(int index);
@@ -91,6 +90,12 @@ private:
int getCurrentIconSize() const;
void addIconSizes(int current);
void translateIconSizes();
int getCurrentMenuIconSize() const;
void addMenuIconSizes(int current);
void translateMenuIconSizes();
public:
static void applyMenuIconSize(int pixel);
private:
int localeIndex;

View File

@@ -219,35 +219,35 @@ dot/period will always be printed</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="moreThemesLabel">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Looking for more themes? You can obtain them using the &lt;a href=&quot;freecad:Std_AddonMgr&quot;&gt;Addon Manager&lt;/a&gt;.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="1" column="0">
<widget class="QLabel" name="iconSizeLabel">
<property name="text">
<string>Size of toolbar icons</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="1">
<widget class="QComboBox" name="toolbarIconSize">
<property name="toolTip">
<string>Icon size in the toolbar</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="menuIconSizeLabel">
<property name="text">
<string>Size of menu icons</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="menuIconSize">
<property name="toolTip">
<string>Icon size in context menus and dropdown menus</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="treeModeLabel">
<property name="text">
@@ -278,7 +278,7 @@ dot/period will always be printed</string>
<string>How many files should be listed in recent files list</string>
</property>
<property name="value">
<number>4</number>
<number>10</number>
</property>
<property name="prefEntry" stdset="0">
<cstring>RecentFiles</cstring>

View File

@@ -23,7 +23,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Customize the current theme. The offered settings are optional for theme developers so they may or may not have an effect in the current theme.</string>
<string>Kindred Create Theme</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -392,10 +392,10 @@
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Overlay</string>
<string>Panel Visibility</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0">
<item row="0" column="0">
<widget class="Gui::PrefCheckBox" name="hideTabBarCheckBox">
<property name="toolTip">
<string>Hide tab bar in dock overlay</string>
@@ -414,23 +414,7 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="Gui::PrefCheckBox" name="hintShowTabBarCheckBox">
<property name="toolTip">
<string>Show tab bar on mouse over when auto hide</string>
</property>
<property name="text">
<string>Hint show tab bar</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>DockOverlayHintTabBar</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>View</cstring>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="1" column="0">
<widget class="Gui::PrefCheckBox" name="hidePropertyViewScrollBarCheckBox">
<property name="toolTip">
<string>Hide property view scroll bar in dock overlay</string>
@@ -446,7 +430,7 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="2" column="0">
<widget class="Gui::PrefCheckBox" name="overlayAutoHideCheckBox">
<property name="toolTip">
<string>Automatically hide overlaid dock panels when in non 3D view (e.g. TechDraw or Spreadsheet)</string>
@@ -465,13 +449,22 @@
</property>
</widget>
</item>
<item row="6" column="0">
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3b">
<property name="title">
<string>Overlay Interaction</string>
</property>
<layout class="QGridLayout" name="gridLayout_2b">
<item row="0" column="0">
<widget class="Gui::PrefCheckBox" name="mouseClickPassThroughCheckBox">
<property name="toolTip">
<string>Auto mouse click through transparent part of dock overlay.</string>
<string>Auto mouse click through transparent part of dock overlay</string>
</property>
<property name="text">
<string>Automatically pass through of the mouse cursor</string>
<string>Automatically pass through mouse cursor</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -484,13 +477,13 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="1" column="0">
<widget class="Gui::PrefCheckBox" name="mouseWheelPassThroughCheckBox">
<property name="toolTip">
<string>Automatically passes mouse wheel events through the transparent areas of an overlay panel</string>
</property>
<property name="text">
<string>Automatically pass through of the mouse wheel</string>
<string>Automatically pass through mouse wheel</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -503,6 +496,22 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="Gui::PrefCheckBox" name="hintShowTabBarCheckBox">
<property name="toolTip">
<string>Show tab bar on mouse over when auto hide</string>
</property>
<property name="text">
<string>Hint show tab bar</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>DockOverlayHintTabBar</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>View</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -56,6 +56,7 @@
#include "Language/Translator.h"
#include "Dialogs/DlgVersionMigrator.h"
#include "FreeCADStyle.h"
#include "PreferencePages/DlgSettingsGeneral.h"
#include <App/Application.h>
#include <Base/Console.h>
@@ -226,6 +227,7 @@ void StartupPostProcess::execute()
setProcessMessages();
setAutoSaving();
setToolBarIconSize();
setMenuIconSize();
setWheelEventFilter();
setLocale();
setCursorFlashing();
@@ -281,6 +283,15 @@ void StartupPostProcess::setToolBarIconSize()
}
}
void StartupPostProcess::setMenuIconSize()
{
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
int size = int(hGrp->GetInt("MenuIconSize", 0));
if (size >= 16) {
Dialog::DlgSettingsGeneral::applyMenuIconSize(size);
}
}
void StartupPostProcess::setWheelEventFilter()
{
// filter wheel events for combo boxes

View File

@@ -64,6 +64,7 @@ private:
void setProcessMessages();
void setAutoSaving();
void setToolBarIconSize();
void setMenuIconSize();
void setWheelEventFilter();
void setLocale();
void setCursorFlashing();

View File

@@ -1,11 +1,20 @@
# Kindred Create core module
# Handles auto-loading of ztools and Silo addons
# Generate version.py from template with Kindred Create version
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/version.py.in
${CMAKE_CURRENT_BINARY_DIR}/version.py
@ONLY
)
# Install Python init files
install(
FILES
Init.py
InitGui.py
update_checker.py
${CMAKE_CURRENT_BINARY_DIR}/version.py
DESTINATION
Mod/Create
)

View File

@@ -65,34 +65,15 @@ def _check_silo_first_start():
FreeCAD.Console.PrintLog(f"Create: Silo first-start check skipped: {e}\n")
def _setup_silo_menu():
"""Inject Silo commands into the File menu and toolbar across all workbenches."""
def _register_silo_origin():
"""Register Silo as a file origin so the origin selector can offer it."""
try:
# Import silo_commands eagerly so commands are registered before the
# manipulator tries to add them to toolbars/menus.
import silo_commands # noqa: F401
import silo_commands # noqa: F401 - registers Silo commands
import silo_origin
class SiloMenuManipulator:
def modifyMenuBar(self):
return [
{"insert": "Silo_New", "menuItem": "Std_New", "after": ""},
{"insert": "Silo_Open", "menuItem": "Std_Open", "after": ""},
{"insert": "Silo_Save", "menuItem": "Std_Save", "after": ""},
{"insert": "Silo_Commit", "menuItem": "Silo_Save", "after": ""},
{"insert": "Silo_Pull", "menuItem": "Silo_Commit", "after": ""},
{"insert": "Silo_Push", "menuItem": "Silo_Pull", "after": ""},
{"insert": "Silo_BOM", "menuItem": "Silo_Push", "after": ""},
]
def modifyToolBars(self):
return [
{"append": "Silo_ToggleMode", "toolBar": "File"},
]
FreeCADGui.addWorkbenchManipulator(SiloMenuManipulator())
FreeCAD.Console.PrintLog("Create: Silo menu manipulator installed\n")
silo_origin.register_silo_origin()
except Exception as e:
FreeCAD.Console.PrintLog(f"Create: Silo menu setup skipped: {e}\n")
FreeCAD.Console.PrintLog(f"Create: Silo origin registration skipped: {e}\n")
def _setup_silo_auth_panel():
@@ -167,13 +148,24 @@ def _setup_silo_activity_panel():
FreeCAD.Console.PrintLog(f"Create: Silo activity panel skipped: {e}\n")
def _check_for_updates():
"""Check for application updates in the background."""
try:
from update_checker import _run_update_check
_run_update_check()
except Exception as e:
FreeCAD.Console.PrintLog(f"Create: Update check skipped: {e}\n")
# Defer enhancements until the GUI event loop is running
try:
from PySide.QtCore import QTimer
QTimer.singleShot(1500, _setup_silo_auth_panel)
QTimer.singleShot(2000, _setup_silo_menu)
QTimer.singleShot(1500, _register_silo_origin)
QTimer.singleShot(2000, _setup_silo_auth_panel)
QTimer.singleShot(3000, _check_silo_first_start)
QTimer.singleShot(4000, _setup_silo_activity_panel)
QTimer.singleShot(10000, _check_for_updates)
except Exception:
pass

View File

@@ -0,0 +1,165 @@
"""Kindred Create update checker.
Queries the Gitea releases API to determine if a newer version is
available. Designed to run in the background on startup without
blocking the UI.
"""
import json
import re
import urllib.request
from datetime import datetime, timezone
import FreeCAD
_RELEASES_URL = "https://git.kindred-systems.com/api/v1/repos/kindred/create/releases"
_PREF_PATH = "User parameter:BaseApp/Preferences/Mod/KindredCreate/Update"
_TIMEOUT = 5
def _parse_version(tag):
"""Parse a version tag like 'v0.1.3' into a comparable tuple.
Returns None if the tag doesn't match the expected pattern.
"""
m = re.match(r"^v?(\d+)\.(\d+)\.(\d+)$", tag)
if not m:
return None
return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
def check_for_update(current_version):
"""Check if a newer release is available on Gitea.
Args:
current_version: Version string like "0.1.3".
Returns:
Dict with update info if a newer version exists, None otherwise.
Dict keys: version, tag, release_url, assets, body.
"""
current = _parse_version(current_version)
if current is None:
return None
req = urllib.request.Request(
f"{_RELEASES_URL}?limit=10",
headers={"Accept": "application/json"},
)
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
releases = json.loads(resp.read())
best = None
best_version = current
for release in releases:
if release.get("draft"):
continue
if release.get("prerelease"):
continue
tag = release.get("tag_name", "")
# Skip the rolling 'latest' tag
if tag == "latest":
continue
ver = _parse_version(tag)
if ver is None:
continue
if ver > best_version:
best_version = ver
best = release
if best is None:
return None
assets = []
for asset in best.get("assets", []):
assets.append(
{
"name": asset.get("name", ""),
"url": asset.get("browser_download_url", ""),
"size": asset.get("size", 0),
}
)
return {
"version": ".".join(str(x) for x in best_version),
"tag": best["tag_name"],
"release_url": best.get("html_url", ""),
"assets": assets,
"body": best.get("body", ""),
}
def _should_check(param):
"""Determine whether an update check should run now.
Args:
param: FreeCAD parameter group for update preferences.
Returns:
True if a check should be performed.
"""
if not param.GetBool("CheckEnabled", True):
return False
last_check = param.GetString("LastCheckTimestamp", "")
if not last_check:
return True
interval_days = param.GetInt("CheckIntervalDays", 1)
if interval_days <= 0:
return True
try:
last_dt = datetime.fromisoformat(last_check)
now = datetime.now(timezone.utc)
elapsed = (now - last_dt).total_seconds()
return elapsed >= interval_days * 86400
except (ValueError, TypeError):
return True
def _run_update_check():
"""Entry point called from the deferred startup timer."""
param = FreeCAD.ParamGet(_PREF_PATH)
if not _should_check(param):
return
try:
from version import VERSION
except ImportError:
FreeCAD.Console.PrintLog(
"Create: update check skipped — version module not available\n"
)
return
try:
result = check_for_update(VERSION)
except Exception as e:
FreeCAD.Console.PrintLog(f"Create: update check failed: {e}\n")
return
# Record that we checked
param.SetString(
"LastCheckTimestamp",
datetime.now(timezone.utc).isoformat(),
)
if result is None:
FreeCAD.Console.PrintLog("Create: application is up to date\n")
return
skipped = param.GetString("SkippedVersion", "")
if result["version"] == skipped:
FreeCAD.Console.PrintLog(
f"Create: update {result['version']} available but skipped by user\n"
)
return
FreeCAD.Console.PrintMessage(
f"Kindred Create {result['version']} is available (current: {VERSION})\n"
)

View File

@@ -0,0 +1 @@
VERSION = "@KINDRED_CREATE_VERSION@"

View File

@@ -4,6 +4,7 @@
add_executable(Gui_tests_run
Assistant.cpp
Camera.cpp
OriginManager.cpp
StyleParameters/StyleParametersApplicationTest.cpp
StyleParameters/ParserTest.cpp
StyleParameters/ParameterManagerTest.cpp

View File

@@ -0,0 +1,382 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <gtest/gtest.h>
#include <string>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/PropertyStandard.h>
#include <Gui/FileOrigin.h>
#include <Gui/OriginManager.h>
#include <src/App/InitApplication.h>
namespace
{
/**
* Minimal FileOrigin implementation for testing OriginManager.
* All document operations are stubs -- we only need identity,
* capability, and ownership methods to test the manager.
*/
class MockOrigin : public Gui::FileOrigin
{
public:
explicit MockOrigin(std::string originId,
Gui::OriginType originType = Gui::OriginType::PLM,
bool ownsAll = false)
: _id(std::move(originId))
, _type(originType)
, _ownsAll(ownsAll)
{}
// Identity
std::string id() const override { return _id; }
std::string name() const override { return _id + " Name"; }
std::string nickname() const override { return _id; }
QIcon icon() const override { return {}; }
Gui::OriginType type() const override { return _type; }
// Characteristics
bool tracksExternally() const override { return _type == Gui::OriginType::PLM; }
bool requiresAuthentication() const override { return _type == Gui::OriginType::PLM; }
// Capabilities
bool supportsRevisions() const override { return _supportsRevisions; }
bool supportsBOM() const override { return _supportsBOM; }
bool supportsPartNumbers() const override { return _supportsPartNumbers; }
// Document identity
std::string documentIdentity(App::Document* /*doc*/) const override { return {}; }
std::string documentDisplayId(App::Document* /*doc*/) const override { return {}; }
bool ownsDocument(App::Document* doc) const override
{
if (!doc) {
return false;
}
return _ownsAll;
}
// Document operations (stubs)
App::Document* newDocument(const std::string& /*name*/) override { return nullptr; }
App::Document* openDocument(const std::string& /*identity*/) override { return nullptr; }
App::Document* openDocumentInteractive() override { return nullptr; }
bool saveDocument(App::Document* /*doc*/) override { return false; }
bool saveDocumentAs(App::Document* /*doc*/, const std::string& /*id*/) override
{
return false;
}
bool saveDocumentAsInteractive(App::Document* /*doc*/) override { return false; }
// Test controls
bool _supportsRevisions = true;
bool _supportsBOM = true;
bool _supportsPartNumbers = true;
private:
std::string _id;
Gui::OriginType _type;
bool _ownsAll;
};
} // namespace
// =========================================================================
// LocalFileOrigin identity tests
// =========================================================================
class LocalFileOriginTest : public ::testing::Test
{
protected:
static void SetUpTestSuite()
{
tests::initApplication();
}
void SetUp() override
{
_origin = std::make_unique<Gui::LocalFileOrigin>();
}
Gui::LocalFileOrigin* origin() { return _origin.get(); }
private:
std::unique_ptr<Gui::LocalFileOrigin> _origin;
};
TEST_F(LocalFileOriginTest, LocalOriginId)
{
EXPECT_EQ(origin()->id(), "local");
}
TEST_F(LocalFileOriginTest, LocalOriginName)
{
EXPECT_EQ(origin()->name(), "Local Files");
}
TEST_F(LocalFileOriginTest, LocalOriginNickname)
{
EXPECT_EQ(origin()->nickname(), "Local");
}
TEST_F(LocalFileOriginTest, LocalOriginType)
{
EXPECT_EQ(origin()->type(), Gui::OriginType::Local);
}
TEST_F(LocalFileOriginTest, LocalOriginCapabilities)
{
EXPECT_FALSE(origin()->tracksExternally());
EXPECT_FALSE(origin()->requiresAuthentication());
EXPECT_FALSE(origin()->supportsRevisions());
EXPECT_FALSE(origin()->supportsBOM());
EXPECT_FALSE(origin()->supportsPartNumbers());
EXPECT_FALSE(origin()->supportsAssemblies());
}
// =========================================================================
// OriginManager tests
// =========================================================================
class OriginManagerTest : public ::testing::Test
{
protected:
static void SetUpTestSuite()
{
tests::initApplication();
}
void SetUp() override
{
// Ensure clean singleton state for each test
Gui::OriginManager::destruct();
_mgr = Gui::OriginManager::instance();
}
void TearDown() override
{
Gui::OriginManager::destruct();
}
Gui::OriginManager* mgr() { return _mgr; }
private:
Gui::OriginManager* _mgr = nullptr;
};
// --- Registration ---
TEST_F(OriginManagerTest, LocalOriginAlwaysPresent)
{
auto* local = mgr()->getOrigin("local");
ASSERT_NE(local, nullptr);
EXPECT_EQ(local->id(), "local");
}
TEST_F(OriginManagerTest, RegisterCustomOrigin)
{
auto* raw = new MockOrigin("test-plm");
EXPECT_TRUE(mgr()->registerOrigin(raw));
EXPECT_EQ(mgr()->getOrigin("test-plm"), raw);
}
TEST_F(OriginManagerTest, RejectDuplicateId)
{
mgr()->registerOrigin(new MockOrigin("dup"));
// Second registration with same ID should fail
EXPECT_FALSE(mgr()->registerOrigin(new MockOrigin("dup")));
}
TEST_F(OriginManagerTest, RejectNullOrigin)
{
EXPECT_FALSE(mgr()->registerOrigin(nullptr));
}
TEST_F(OriginManagerTest, OriginIdsIncludesAll)
{
mgr()->registerOrigin(new MockOrigin("alpha"));
mgr()->registerOrigin(new MockOrigin("beta"));
auto ids = mgr()->originIds();
EXPECT_EQ(ids.size(), 3u); // local + alpha + beta
auto has = [&](const std::string& id) {
return std::find(ids.begin(), ids.end(), id) != ids.end();
};
EXPECT_TRUE(has("local"));
EXPECT_TRUE(has("alpha"));
EXPECT_TRUE(has("beta"));
}
// --- Unregistration ---
TEST_F(OriginManagerTest, UnregisterCustomOrigin)
{
mgr()->registerOrigin(new MockOrigin("removable"));
EXPECT_TRUE(mgr()->unregisterOrigin("removable"));
EXPECT_EQ(mgr()->getOrigin("removable"), nullptr);
}
TEST_F(OriginManagerTest, CannotUnregisterLocal)
{
EXPECT_FALSE(mgr()->unregisterOrigin("local"));
EXPECT_NE(mgr()->getOrigin("local"), nullptr);
}
TEST_F(OriginManagerTest, UnregisterCurrentSwitchesToLocal)
{
mgr()->registerOrigin(new MockOrigin("ephemeral"));
mgr()->setCurrentOrigin("ephemeral");
EXPECT_EQ(mgr()->currentOriginId(), "ephemeral");
mgr()->unregisterOrigin("ephemeral");
EXPECT_EQ(mgr()->currentOriginId(), "local");
}
// --- Current origin selection ---
TEST_F(OriginManagerTest, DefaultCurrentIsLocal)
{
EXPECT_EQ(mgr()->currentOriginId(), "local");
EXPECT_NE(mgr()->currentOrigin(), nullptr);
}
TEST_F(OriginManagerTest, SetCurrentOrigin)
{
mgr()->registerOrigin(new MockOrigin("plm1"));
std::string notified;
auto conn = mgr()->signalCurrentOriginChanged.connect([&](const std::string& id) {
notified = id;
});
EXPECT_TRUE(mgr()->setCurrentOrigin("plm1"));
EXPECT_EQ(mgr()->currentOriginId(), "plm1");
EXPECT_EQ(notified, "plm1");
}
TEST_F(OriginManagerTest, SetCurrentRejectsUnknown)
{
EXPECT_FALSE(mgr()->setCurrentOrigin("nonexistent"));
EXPECT_EQ(mgr()->currentOriginId(), "local");
}
TEST_F(OriginManagerTest, SetCurrentSameIdNoSignal)
{
int signalCount = 0;
auto conn = mgr()->signalCurrentOriginChanged.connect([&](const std::string&) {
signalCount++;
});
mgr()->setCurrentOrigin("local"); // already current
EXPECT_EQ(signalCount, 0);
}
// --- Document ownership ---
class OriginManagerDocTest : public ::testing::Test
{
protected:
static void SetUpTestSuite()
{
tests::initApplication();
}
void SetUp() override
{
Gui::OriginManager::destruct();
_mgr = Gui::OriginManager::instance();
_docName = App::GetApplication().getUniqueDocumentName("test");
_doc = App::GetApplication().newDocument(_docName.c_str(), "testUser");
}
void TearDown() override
{
App::GetApplication().closeDocument(_docName.c_str());
Gui::OriginManager::destruct();
}
Gui::OriginManager* mgr() { return _mgr; }
App::Document* doc() { return _doc; }
private:
Gui::OriginManager* _mgr = nullptr;
std::string _docName;
App::Document* _doc = nullptr;
};
TEST_F(OriginManagerDocTest, LocalOwnsPlainDocument)
{
// A document with no SiloItemId property should be owned by local
auto* local = mgr()->getOrigin("local");
ASSERT_NE(local, nullptr);
EXPECT_TRUE(local->ownsDocument(doc()));
}
TEST_F(OriginManagerDocTest, LocalDisownsTrackedDocument)
{
// Add an object with a SiloItemId property -- local should reject ownership
auto* obj = doc()->addObject("App::FeaturePython", "TrackedPart");
ASSERT_NE(obj, nullptr);
obj->addDynamicProperty("App::PropertyString", "SiloItemId");
auto* prop = dynamic_cast<App::PropertyString*>(obj->getPropertyByName("SiloItemId"));
ASSERT_NE(prop, nullptr);
prop->setValue("some-uuid");
auto* local = mgr()->getOrigin("local");
EXPECT_FALSE(local->ownsDocument(doc()));
}
TEST_F(OriginManagerDocTest, FindOwningOriginPrefersNonLocal)
{
// Register a PLM mock that claims ownership of everything
auto* plm = new MockOrigin("test-plm", Gui::OriginType::PLM, /*ownsAll=*/true);
mgr()->registerOrigin(plm);
auto* owner = mgr()->findOwningOrigin(doc());
ASSERT_NE(owner, nullptr);
EXPECT_EQ(owner->id(), "test-plm");
}
TEST_F(OriginManagerDocTest, FindOwningOriginFallsBackToLocal)
{
// No PLM origins registered -- should fall back to local
auto* owner = mgr()->findOwningOrigin(doc());
ASSERT_NE(owner, nullptr);
EXPECT_EQ(owner->id(), "local");
}
TEST_F(OriginManagerDocTest, OriginForNewDocumentReturnsCurrent)
{
mgr()->registerOrigin(new MockOrigin("plm2"));
mgr()->setCurrentOrigin("plm2");
auto* origin = mgr()->originForNewDocument();
ASSERT_NE(origin, nullptr);
EXPECT_EQ(origin->id(), "plm2");
}
TEST_F(OriginManagerDocTest, SetAndClearDocumentOrigin)
{
auto* local = mgr()->getOrigin("local");
mgr()->setDocumentOrigin(doc(), local);
EXPECT_EQ(mgr()->originForDocument(doc()), local);
mgr()->clearDocumentOrigin(doc());
// After clearing, originForDocument falls back to ownership detection
auto* resolved = mgr()->originForDocument(doc());
ASSERT_NE(resolved, nullptr);
EXPECT_EQ(resolved->id(), "local");
}
TEST_F(OriginManagerDocTest, NullDocumentHandling)
{
EXPECT_EQ(mgr()->findOwningOrigin(nullptr), nullptr);
EXPECT_EQ(mgr()->originForDocument(nullptr), nullptr);
}