Compare commits

...

33 Commits

Author SHA1 Message Date
forbes
84c1776f92 fix: update ztools submodule — robust PartDesign menu insertion (#57)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Replaces fragile chained insert operations in modifyMenuBar() with
independent append operations using PartDesign_Body as parent-locator.

Closes #57
2026-02-08 19:32:31 -06:00
f391c526cd Merge pull request 'fix: update ztools submodule — eager command registration (#52)' (#71) from fix/manipulator-timing into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #71
2026-02-09 01:28:33 +00:00
ac4f01c5c0 Merge branch 'main' into fix/manipulator-timing
Some checks failed
Build and Test / build (pull_request) Has been cancelled
2026-02-09 01:28:20 +00:00
1d2a11572d Merge pull request 'fix(theme): eliminate QSS/CFG duplication (#51)' (#70) from fix/qss-duplication into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #70
2026-02-09 01:28:07 +00:00
forbes
b3a6c9d925 merge: resolve conflicts with main (docs)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
2026-02-08 19:27:52 -06:00
7c84eeb68d Merge pull request 'docs: repository cleanup and documentation update' (#50) from chore/repo-cleanup-docs into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #50
2026-02-09 01:26:31 +00:00
forbes
0a0ac547ef fix: update ztools submodule — eager command registration (#52)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Updates ztools to include the manipulator timing fix. Commands and the
PartDesign manipulator are now registered at module scope instead of
during ZToolsWorkbench.Initialize(), ensuring ztools buttons appear in
PartDesign regardless of workbench activation order.
2026-02-08 17:58:29 -06:00
forbes
6773ca0dfd fix(theme): eliminate QSS/CFG duplication (#51)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
- Make src/Gui/Stylesheets/KindredCreate.qss the single source of truth
- Generate PreferencePacks QSS copy at build time via configure_file()
- Merge missing preference defaults into PreferencePacks cfg:
  Document (undo, autosave, backup, license), TreeView (PreSelection,
  SyncView, SyncSelection), NotificationArea, OutputWindow recording,
  General (AutoloadModule=ZToolsWorkbench)
- Delete unused resources/preferences/KindredCreate/ directory
- Delete src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss from source
- Update documentation to reflect single QSS location
2026-02-08 17:41:26 -06:00
forbes
82e7362a56 docs: repository cleanup and documentation update
Some checks failed
Build and Test / build (pull_request) Has been cancelled
- Rewrite CONTRIBUTING.md with Kindred-specific contribution guide
  (branch workflow, commit conventions, submodule management, QSS procedures)
- Update README.md with Origin system, update checker, and preference defaults
- Update OVERVIEW.md with current submodule pins and date
- Update KNOWN_ISSUES.md with resolved items and new Silo features
- Update COMPONENTS.md with SSE, Activity panel, Start Panel, theme additions
- Update INTEGRATION_PLAN.md phase status (theme, Silo, build system)
- Update ARCHITECTURE.md bootstrap flow and source layout
2026-02-08 17:22:59 -06:00
cf523f1d87 Merge pull request 'fix(build): fix DlgSettingsGeneral::applyMenuIconSize visibility and namespace' (#49) from fix/build-menu-icon-size into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #49
2026-02-08 23:05:10 +00:00
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
56 changed files with 1800 additions and 3122 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

@@ -1,111 +1,131 @@
# FreeCAD Contribution Process (FCP)
# Contributing to Kindred Create
FreeCAD's contribution process is inspired by the Collective Code Construction Contract which itself is an evolution of the github.com Fork and Pull Model.
Kindred Create is maintained at [git.kindred-systems.com/kindred/create](https://git.kindred-systems.com/kindred/create). Contributions are submitted as pull requests against the `main` branch.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
## Getting started
```bash
git clone --recursive ssh://git@git.kindred-systems.com:2222/kindred/create.git
cd create
pixi run configure
pixi run build
pixi run freecad
```
## 0. Status
See the [README](README.md) for full build instructions.
FreeCAD is in a transition period. The following are to be regarded as GUIDELINES for contribution submission and acceptance. For historical reasons, the actual process MAY diverge from this process during the transition. Such deviations SHOULD be noted and discussed whenever possible.
## Branch and PR workflow
## 1. Goals
1. Create a feature branch from `main`:
```bash
git checkout -b feat/my-feature main
```
2. Make your changes, commit with conventional commit messages (see below).
3. Push and open a pull request against `main`.
4. CI builds and tests run automatically on all PRs.
The FreeCAD Contribution Process is expressed here with the following specific goals in mind:
## Commit messages
1. To provide transparency and fairness in the contribution process.
2. To allow contributions to be included as quickly as possible.
3. To preserve and improve the code quality while encouraging appropriate experimentation and risk-taking.
4. To minimize dependence on individual Contributors by encouraging a large pool of active Contributors.
5. To be inclusive of many viewpoints and to harness a diverse set of skills.
6. To provide an encouraging environment where Contributors learn and improve their skills.
7. To protect the free and open nature of the FreeCAD project.
Use [Conventional Commits](https://www.conventionalcommits.org/):
## 2. Fundamentals
| Prefix | Purpose |
|--------|---------|
| `feat:` | New feature |
| `fix:` | Bug fix |
| `chore:` | Maintenance, dependencies |
| `docs:` | Documentation only |
| `art:` | Icons, theme, visual assets |
1. FreeCAD uses the git distributed revision control system.
2. Source code for the main application and related subprojects is hosted on github.com in the FreeCAD organization.
3. Problems are discrete, well-defined limitations or bugs.
4. FreeCAD uses GitHub's issue-tracking system to track problems and contributions. For help requests and general discussions, use the project forum.
5. Contributions are sets of code changes that resolve a single problem.
6. FreeCAD uses the Pull Request workflow for evaluating and accepting contributions.
Examples:
- `feat: add datum point creation mode`
- `fix(gui): correct menu icon size on Wayland`
- `chore: update silo submodule`
## 3. Roles
1. "User": A member of the wider FreeCAD community who uses the software.
2. "Contributor": A person who submits a contribution that resolves a previously identified problem. Contributors do not have commit access to the repository unless they are also Maintainers. Everyone, without distinction or discrimination, SHALL have an equal right to become a Contributor.
3. "Maintainer": A person who merges contributions. Maintainers may or may not be Contributors. Their role is to enforce the process. Maintainers have commit access to the repository.
4. "Administrator": Administrators have additional authority to maintain the list of designated Maintainers.
## Code style
## 4. Licensing, Ownership, and Credit
1. FreeCAD is distributed under the Lesser General Public License, version 2, or superior (LGPL2+). Additional details can be found in the LICENSE file.
2. All contributions to FreeCAD MUST use a compatible license.
3. All contributions are owned by their authors unless assigned to another.
4. FreeCAD does not have a mandatory copyright assignment policy.
5. A Contributor who wishes to be identified in the Credits section of the application "About" dialog is responsible for identifying themselves. They should modify the Contributors file and submit a PR with a single commit for this modification only. The contributors file is found at https://github.com/FreeCAD/FreeCAD/blob/main/src/Doc/CONTRIBUTORS
6. A contributor who does not wish to assume the copyright of their contribution MAY choose to assign it to the [FreeCAD project association](https://fpa.freecad.org) by mentioning **Copyright (c) 2022 The FreeCAD project association <fpa@freecad.org>** in the file's license code block.
### C/C++
## 5. Contribution Requirements
Formatted with **clang-format** (config in `.clang-format`). Static analysis via **clang-tidy** (config in `.clang-tidy`).
1. Contributions are submitted in the form of Pull Requests (PR).
2. Maintainers and Contributors MUST have a GitHub account and SHOULD use their real names or a well-known alias.
3. If the GitHub username differs from the username on the FreeCAD Forum, effort SHOULD be taken to avoid confusion.
4. A PR SHOULD be a minimal and accurate answer to exactly one identified and agreed-on problem.
5. A PR SHOULD refrain from adding additional dependencies to the FreeCAD project unless no other option is available.
6. Code submissions MUST adhere to the code style guidelines of the project if these are defined.
7. If a PR contains multiple commits, each commit MUST compile cleanly when merged with all previous commits of the same PR. Each commit SHOULD add value to the history of the project. Checkpoint commits SHOULD be squashed.
8. A PR SHALL NOT include non-trivial code from other projects unless the Contributor is the original author of that code.
9. A PR MUST compile cleanly and pass project self-tests on all target platforms.
10. Changes that break python API used by extensions SHALL be avoided. If it is not possible to avoid breaking changes, the amount of them MUST be minimized and PR MUST clearly describe all breaking changes with clear description on how to replace no longer working solution with newer one. Contributor SHOULD search for addons that will be broken and list them in the PR.
11. Each commit message in a PR MUST succinctly explain what the commit achieves. The commit message SHALL follow the suggestions in the `git commit --help` documentation, section DISCUSSION.
12. The PR Title MUST succinctly explain what the PR achieves. The Body MAY be as detailed as needed. If a PR changes the user interface (UI), the body of the text MUST include a presentation of these UI changes, preferably with screenshots of the previous and revised state.
13. If a PR contains the work of another author (for example, if it is cherry-picked from a fork by someone other than the PR-submitter):
1. the PR description MUST contain proper attribution as the first line, for example: "This is work of XYZ cherry-picked from <link>";
2. all commits MUST have proper authorship, i.e. be authored by the original author and committed by the author of the PR;
3. if changes to cherry-picked commits are necessary they SHOULD be done as follow-up commits. If it is not possible to do so, then the modified commits MUST contain a `Co-Authored-By` trailer in their commit message.
14. A “Valid PR” is one which satisfies the above requirements.
### Python
## 6. Process
Formatted with **black** (100-character line length). Linted with **pylint** (config in `.pylintrc`).
1. Change on the project follows the pattern of accurately identifying problems and applying minimal, accurate solutions to these problems.
2. To request changes, a User logs an issue on the project GitHub issue tracker.
3. The User or Contributor SHOULD write the issue by describing the problem they face or observe. Links to the forum or other resources are permitted but the issue SHOULD be complete and accurate and SHOULD NOT require the reader to visit the forum or any other platform to understand what is being described.
4. Issue authors SHOULD strive to describe the minimum acceptable condition.
5. Issue authors SHOULD focus on User tasks and avoid comparisons to other software solutions.
6. The User or Contributor SHOULD seek consensus on the accuracy of their observation and the value of solving the problem.
7. To submit a solution to a problem, a Contributor SHALL create a pull request back to the project.
8. Contributors and Maintainers SHALL NOT commit changes directly to the target branch.
9. To discuss a proposed solution, Users MAY comment on the Pull Request in GitHub. Forum conversations regarding the solution SHOULD be discouraged and conversation redirected to the Pull Request or the related issue.
10. To accept or reject a Pull Request, a Maintainer SHALL use GitHub's interface.
11. Maintainers SHOULD NOT merge their own PRs except:
1. in exceptional cases, such as non-responsiveness from other Maintainers for an extended period.
2. If the Maintainer is also the primary developer of the workbench or subsystem.
### Pre-commit hooks
12. Maintainers SHALL merge valid PRs from other Contributors rapidly.
13. Maintainers MAY, at their discretion merge PRs that have not met all criteria to be considered valid to:
1. end fruitless discussions
2. capture toxic contributions in the historical record
3. engage with the Contributor on improving their contribution quality.
14. Maintainers SHALL NOT make value judgments on correct contributions.
15. If a PR requires significant further work before merging, the PR SHOULD be moved to draft status.
16. If a PR is complete, but should not be merged yet (for example, because it depends on another in-process PR), the "On hold" label SHOULD be applied.
17. Any Contributor who has value judgments on a PR SHOULD express these via their own PR.
18. The User who created an issue SHOULD close the issue after checking the PR is successful.
19. Maintainers SHOULD close issues that are left open without action or update for an unreasonable period.
```bash
pip install pre-commit
pre-commit install
```
## 7. Branches and Releases
This runs clang-format, black, and pylint automatically on staged files.
1. The project SHALL have one branch (“main”) that always holds the latest in-progress version and SHOULD always build.
2. The project SHALL NOT use topic branches for any reason. Personal forks MAY use topic branches.
3. To make a stable release a Maintainer SHALL tag the repository. Stable releases SHALL always be released from the repository main branch.
## Submodules
## 8. Project Administration
Kindred Create uses git submodules for addon workbenches:
1. Project Administrators are those individuals who are members of the FreeCAD Github organization and have the role of 'owner'. They have the task of administering the organization including adding and removing individuals from various teams.
2. Project Administrator is a technical role necessitated by the GitHub platform. Except for the specific exceptions listed below, the Project Administrators do not make the decision about individual team members. Rather, they carry out the collective wishes of the Maintainers team. Project Administrators will be selected from the Maintainers team by the Maintainers themselves.
3. To ensure continuity there SHALL be at least four Project Administrators at all times.
4. The project Administrators will manage the set of project Maintainers. They SHALL maintain a sufficiently large pool of Maintainers to ensure their succession and permit timely review of contributions. If the pool of Maintainers is insufficient, the Project Administrators will request that the Maintainers select additional individuals to add.
5. Contributors who have a history of successful PRs and have demonstrated continued professionalism should be invited to be Maintainers.
6. Administrators SHOULD remove Maintainers who are inactive for an extended period, or who repeatedly fail to apply this process accurately.
7. The list of Maintainers SHALL be publicly accessible and reflective of current activity on the project.
8. Administrators SHALL act expediently to protect the FreeCAD infrastructure and resources.
9. Administrators SHOULD block or ban “bad actors” who cause stress, animosity, or confusion to others in the project. This SHOULD be done after public discussion, with a chance for all parties to speak. A bad actor is someone who repeatedly ignores the rules and culture of the project, who is hostile or offensive, who impedes the productive exchange of information, and who is unable to self-correct their behavior when asked to do so by others.
| Submodule | Path | Repository |
|-----------|------|------------|
| ztools | `mods/ztools` | `git.kindred-systems.com/forbes/ztools` |
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` |
To update a submodule:
```bash
cd mods/silo
git checkout main && git pull
cd ../..
git add mods/silo
git commit -m "chore: update silo submodule"
```
If you cloned without `--recursive`, initialize submodules with:
```bash
git submodule update --init --recursive
```
## Theme and QSS changes
The Catppuccin Mocha theme has **three QSS copies** that must be kept in sync:
1. `resources/preferences/KindredCreate/KindredCreate.qss` (canonical)
2. `src/Gui/Stylesheets/KindredCreate.qss`
3. `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss`
When modifying the theme, apply changes to all three files. Note that the copies have intentional differences (e.g., tree branch rendering style), so do not blindly copy between them -- apply edits individually.
See [KNOWN_ISSUES.md](docs/KNOWN_ISSUES.md) for the planned QSS consolidation.
## Preference pack
Default preferences are defined in `resources/preferences/KindredCreate/KindredCreate.cfg`. This XML file uses FreeCAD's parameter format:
```xml
<FCParamGroup Name="GroupName">
<FCBool Name="Setting" Value="1"/>
<FCInt Name="Setting" Value="42"/>
<FCText Name="Setting">value</FCText>
</FCParamGroup>
```
Changes here affect the out-of-box experience for all users.
## CI/CD
- **Build workflow** (`build.yml`): Runs on every push to `main` and on PRs. Builds in Ubuntu 24.04 container, runs C++ and Python tests.
- **Release workflow** (`release.yml`): Triggered by `v*` tags. Builds AppImage and .deb packages.
See [docs/CI_CD.md](docs/CI_CD.md) for full details.
## Architecture
For an overview of the codebase structure, bootstrap flow, and design decisions, see:
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) -- Bootstrap flow and source layout
- [docs/COMPONENTS.md](docs/COMPONENTS.md) -- Feature inventory
- [docs/INTEGRATION_PLAN.md](docs/INTEGRATION_PLAN.md) -- Architecture layers and roadmap
## License
All contributions must be compatible with [LGPL-2.1-or-later](LICENSE).

File diff suppressed because one or more lines are too long

View File

@@ -17,10 +17,11 @@ 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
─ 4000ms: _setup_silo_activity_panel() → "Database Activity" dock (SSE)
└─ 10000ms: _check_for_updates() → update checker (Gitea API)
```
## Key source layout
@@ -28,7 +29,9 @@ FreeCAD startup
```
src/Mod/Create/ Kindred bootstrap module (Python)
├── Init.py Adds mods/ addon paths, loads Init.py files
── InitGui.py Loads workbenches, installs Silo manipulators
── InitGui.py Loads workbenches, installs Silo manipulators
├── version.py.in CMake template → version.py (build-time)
└── update_checker.py Checks Gitea releases API for updates
src/Gui/FileOrigin.h/.cpp FileOrigin base class + LocalFileOrigin
src/Gui/CommandOrigin.cpp Origin_Commit/Pull/Push/Info/BOM commands
@@ -52,7 +55,7 @@ mods/silo/ [submodule -> silo-mod.git] FreeCAD workbench
└── silo_origin.py FileOrigin backend for Silo
src/Gui/Stylesheets/ QSS themes and SVG assets
resources/preferences/ Canonical preference pack (KindredCreate)
src/Gui/PreferencePacks/ KindredCreate preference pack (cfg + build-time QSS)
```
See [INTEGRATION_PLAN.md](INTEGRATION_PLAN.md) for architecture layers and phase status.

View File

@@ -62,14 +62,19 @@ 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.
**Dock panels:**
- Database Auth (2000ms) -- Login/logout and API token management
- Database Activity (4000ms) -- Real-time server event feed via SSE (Server-Sent Events) with automatic reconnection and exponential backoff
- Start Panel -- In-viewport landing page with recent files and Silo integration
**Server architecture:** Go REST API (38+ routes) + PostgreSQL + MinIO S3. Authentication via local (bcrypt), LDAP, or OIDC backends. SSE endpoint for real-time event streaming. See `mods/silo/docs/` for server documentation.
**LibreOffice Calc extension** ([silo-calc](https://git.kindred-systems.com/kindred/silo-calc.git)): BOM management, item creation, and AI-assisted descriptions via OpenRouter API. Shares the same Silo REST API and auth token system via the shared [silo-client](https://git.kindred-systems.com/kindred/silo-client.git) package.
@@ -77,13 +82,14 @@ These appear in the File menu and "Origin Tools" toolbar across all workbenches
## Theme
**Canonical source:** `resources/preferences/KindredCreate/KindredCreate.qss`
**Canonical source:** `src/Gui/Stylesheets/KindredCreate.qss`
Four copies must stay in sync:
1. `resources/preferences/KindredCreate/KindredCreate.qss` (canonical)
2. `src/Gui/Stylesheets/KindredCreate.qss`
3. `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss`
4. `mods/ztools/CatppuccinMocha/CatppuccinMocha.qss`
The PreferencePacks copy (`src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss`) is generated at build time via `configure_file()` in `src/Gui/PreferencePacks/CMakeLists.txt`. Only the Stylesheets copy needs to be maintained.
Notable theme customizations beyond standard Catppuccin colors:
- `QGroupBox::indicator` styling to match `QCheckBox::indicator` (consistent checkbox appearance)
- `QLabel[haslink="true"]` link color (`#b4befe` Catppuccin Lavender) -- picked up by FreeCAD to set `QPalette::Link`
- Spanning-line tree branch indicators
---

File diff suppressed because one or more lines are too long

View File

@@ -4,11 +4,11 @@
### Critical
1. **QSS duplication.** Four copies of the stylesheet must be kept in sync manually. A build step or symlinks should eliminate this.
1. ~~**QSS duplication.**~~ Resolved. The canonical QSS lives in `src/Gui/Stylesheets/KindredCreate.qss`. The PreferencePacks copy is now generated at build time via `configure_file()` in `src/Gui/PreferencePacks/CMakeLists.txt`. The unused `resources/preferences/KindredCreate/` directory has been removed.
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
@@ -36,6 +36,8 @@
13. **Assembly solver crash on document restore.** `AssemblyObject::onChanged()` called `updateSolveStatus()` when the Group property changed during document restore, triggering the solver while child objects were still deserializing (SIGSEGV). Fixed with `isRestoring()` and `isPerformingTransaction()` guards at `src/Mod/Assembly/App/AssemblyObject.cpp:143`.
14. **`DlgSettingsGeneral::applyMenuIconSize` visibility.** The method was `private` but called from `StartupProcess.cpp`. Fixed by moving to `public` (PR #49). Also required `Dialog::` namespace qualifier in `StartupProcess.cpp`.
---
## Incomplete features
@@ -44,13 +46,16 @@
| Feature | Status | Notes |
|---------|--------|-------|
| Authentication | Local auth complete | LDAP/OIDC backends coded, pending infrastructure |
| Authentication | Local auth complete | LDAP/OIDC backends coded, pending infrastructure. Auth dock panel available. |
| CSRF protection | Implemented | `nosurf` library on web form routes |
| File locking | Not implemented | Needed to prevent concurrent edits |
| Odoo ERP integration | Stub only | Returns "not yet implemented" |
| Part number date segments | Broken | `formatDate()` returns error |
| Location/inventory APIs | Tables exist, no handlers | |
| CSV import rollback | Not implemented | `bom_handlers.go` |
| SSE event streaming | Implemented | Reconnect logic with exponential backoff |
| Database Activity panel | Implemented | Dock panel showing real-time server events |
| Start panel | Implemented | In-viewport start page with recent files and Silo integration |
### ztools
@@ -73,3 +78,7 @@
4. **Build system** -- CMake install rules for `mods/` submodules so packages include ztools and Silo without manual steps.
5. **Test coverage** -- Unit tests for ztools datum creation, Silo FreeCAD commands, and Go API endpoints.
6. **QSS consolidation** -- Eliminate the 3-copy QSS duplication via build-time copy or symlinks. The canonical source is `resources/preferences/KindredCreate/KindredCreate.qss`.
7. **Update notification UI** -- Display in-app notification when a new release is available (issue #30). The update checker backend is already implemented.

View File

@@ -1,7 +1,7 @@
# Kindred Create
**Last updated:** 2026-02-07
**Branch:** main @ `7bec3d5c3b2`
**Last updated:** 2026-02-08
**Branch:** main @ `cf523f1d87a`
**Kindred Create:** v0.1.0
**FreeCAD base:** v1.0.0
@@ -19,9 +19,9 @@
| Submodule | Path | Source | Pinned commit |
|-----------|------|--------|---------------|
| ztools | `mods/ztools` | `git.kindred-systems.com/forbes/ztools` | `d2f94c3` |
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` | `bf0b843` |
| OndselSolver | `src/3rdParty/OndselSolver` | `git.kindred-systems.com/kindred/solver` | `5d1988b` |
| ztools | `mods/ztools` | `git.kindred-systems.com/forbes/ztools` | `3298d1c` |
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` | `f9924d3` |
| OndselSolver | `src/3rdParty/OndselSolver` | `git.kindred-systems.com/kindred/solver` | `fe41fa3` |
| GSL | `src/3rdParty/GSL` | `github.com/microsoft/GSL` | `756c91a` |
| AddonManager | `src/Mod/AddonManager` | `github.com/FreeCAD/AddonManager` | `01e242e` |
| googletest | `tests/lib` | `github.com/google/googletest` | `56efe39` |

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

@@ -1,112 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<FCParameters>
<FCParamGroup Name="Root">
<FCParamGroup Name="BaseApp">
<FCParamGroup Name="Preferences">
<FCParamGroup Name="Editor">
<FCUInt Name="Text" Value="3453416703"/>
<FCUInt Name="Bookmark" Value="3032415999"/>
<FCUInt Name="Breakpoint" Value="4086016255"/>
<FCUInt Name="Keyword" Value="3416717311"/>
<FCUInt Name="Comment" Value="2139095295"/>
<FCUInt Name="Block comment" Value="2139095295"/>
<FCUInt Name="Number" Value="4206069759"/>
<FCUInt Name="String" Value="2799935999"/>
<FCUInt Name="Character" Value="4073902335"/>
<FCUInt Name="Class name" Value="2310339327"/>
<FCUInt Name="Define name" Value="2310339327"/>
<FCUInt Name="Operator" Value="2312199935"/>
<FCUInt Name="Python output" Value="2796290303"/>
<FCUInt Name="Python error" Value="4086016255"/>
<FCUInt Name="Current line highlight" Value="1162304255"/>
</FCParamGroup>
<FCParamGroup Name="OutputWindow">
<FCUInt Name="colorText" Value="3453416703"/>
<FCUInt Name="colorLogging" Value="2497893887"/>
<FCUInt Name="colorWarning" Value="4192382975"/>
<FCUInt Name="colorError" Value="4086016255"/>
</FCParamGroup>
<FCParamGroup Name="View">
<FCUInt Name="BackgroundColor" Value="505294591"/>
<FCUInt Name="BackgroundColor2" Value="286333951"/>
<FCUInt Name="BackgroundColor3" Value="404235775"/>
<FCUInt Name="BackgroundColor4" Value="825378047"/>
<FCBool Name="Simple" Value="0"/>
<FCBool Name="Gradient" Value="1"/>
<FCBool Name="UseBackgroundColorMid" Value="0"/>
<FCUInt Name="HighlightColor" Value="3416717311"/>
<FCUInt Name="SelectionColor" Value="3032415999"/>
<FCUInt Name="PreselectColor" Value="2497893887"/>
<FCUInt Name="DefaultShapeColor" Value="1482387711"/>
<FCBool Name="RandomColor" Value="0"/>
<FCUInt Name="DefaultShapeLineColor" Value="2470768383"/>
<FCUInt Name="DefaultShapeVertexColor" Value="2470768383"/>
<FCUInt Name="BoundingBoxColor" Value="1819509759"/>
<FCUInt Name="AnnotationTextColor" Value="3453416703"/>
<FCUInt Name="SketchEdgeColor" Value="3453416703"/>
<FCUInt Name="SketchVertexColor" Value="3453416703"/>
<FCUInt Name="EditedEdgeColor" Value="3416717311"/>
<FCUInt Name="EditedVertexColor" Value="4123402495"/>
<FCUInt Name="ConstructionColor" Value="4206069759"/>
<FCUInt Name="ExternalColor" Value="4192382975"/>
<FCUInt Name="FullyConstrainedColor" Value="2799935999"/>
<FCUInt Name="InternalAlignedGeoColor" Value="1959907071"/>
<FCUInt Name="FullyConstraintElementColor" Value="2799935999"/>
<FCUInt Name="FullyConstraintConstructionElementColor" Value="2497893887"/>
<FCUInt Name="FullyConstraintInternalAlignmentColor" Value="2312199935"/>
<FCUInt Name="FullyConstraintConstructionPointColor" Value="2799935999"/>
<FCUInt Name="ConstrainedIcoColor" Value="2310339327"/>
<FCUInt Name="NonDrivingConstrDimColor" Value="2139095295"/>
<FCUInt Name="ConstrainedDimColor" Value="3416717311"/>
<FCUInt Name="ExprBasedConstrDimColor" Value="4206069759"/>
<FCUInt Name="DeactivatedConstrDimColor" Value="1819509759"/>
<FCUInt Name="CursorTextColor" Value="3453416703"/>
<FCUInt Name="CursorCrosshairColor" Value="3416717311"/>
<FCUInt Name="CreateLineColor" Value="2799935999"/>
<FCUInt Name="ShadowLightColor" Value="2470768128"/>
<FCUInt Name="ShadowGroundColor" Value="286333952"/>
<FCUInt Name="HiddenLineColor" Value="825378047"/>
<FCUInt Name="HiddenLineFaceColor" Value="505294591"/>
<FCUInt Name="HiddenLineBackground" Value="505294591"/>
<FCBool Name="EnableBacklight" Value="1"/>
<FCUInt Name="BacklightColor" Value="1162304255"/>
<FCFloat Name="BacklightIntensity" Value="0.30"/>
</FCParamGroup>
<FCParamGroup Name="TreeView">
<FCUInt Name="TreeEditColor" Value="3416717311"/>
<FCUInt Name="TreeActiveColor" Value="2799935999"/>
</FCParamGroup>
<FCParamGroup Name="General">
<FCText Name="AutoloadModule">ZToolsWorkbench</FCText>
</FCParamGroup>
<FCParamGroup Name="MainWindow">
<FCText Name="StyleSheet">KindredCreate.qss</FCText>
</FCParamGroup>
<FCParamGroup Name="Mod">
<FCParamGroup Name="Start">
<FCUInt Name="BackgroundColor1" Value="404235775"/>
<FCUInt Name="BackgroundTextColor" Value="3453416703"/>
<FCUInt Name="PageColor" Value="505294591"/>
<FCUInt Name="PageTextColor" Value="3453416703"/>
<FCUInt Name="BoxColor" Value="825378047"/>
<FCUInt Name="LinkColor" Value="2310339327"/>
<FCUInt Name="BackgroundColor2" Value="286333951"/>
</FCParamGroup>
<FCParamGroup Name="Part">
<FCUInt Name="VertexColor" Value="3032415999"/>
<FCUInt Name="EdgeColor" Value="2310339327"/>
</FCParamGroup>
<FCParamGroup Name="PartDesign">
<FCUInt Name="DefaultDatumColor" Value="3416717311"/>
</FCParamGroup>
<FCParamGroup Name="Draft">
<FCUInt Name="snapcolor" Value="2799935999"/>
</FCParamGroup>
<FCParamGroup Name="Sketcher">
<FCUInt Name="GridLineColor" Value="1162304255"/>
</FCParamGroup>
</FCParamGroup>
</FCParamGroup>
</FCParamGroup>
</FCParamGroup>
</FCParameters>

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>Kindred Create Preference Packs</name>
<description>Default preference packs for Kindred Create, featuring the Catppuccin Mocha color theme.</description>
<version>0.1.0</version>
<maintainer email="support@kindredsystems.net">Kindred Systems LLC</maintainer>
<license>LGPL-2.1-or-later</license>
<url type="website">https://kindredsystems.net</url>
<content>
<preferencepack>
<name>KindredCreate</name>
<description>The default Kindred Create theme based on Catppuccin Mocha - a soothing dark color palette.</description>
</preferencepack>
</content>
</package>

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

@@ -12,6 +12,14 @@ ADD_CUSTOM_TARGET(PreferencePacks_data ALL
FILE(COPY ${PreferencePacks_Files} ${PreferencePacks_Directories} DESTINATION "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks")
# Copy KindredCreate.qss from Stylesheets into the PreferencePacks build directory.
# The canonical QSS lives in src/Gui/Stylesheets/; this avoids maintaining a duplicate. (#51)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/../Stylesheets/KindredCreate.qss
${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks/KindredCreate/KindredCreate.qss
COPYONLY
)
fc_copy_sources(PreferencePacks_data "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks"
${PreferencePacks_Files}
${PreferencePacks_Directories})
@@ -23,9 +31,10 @@ INSTALL(
${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks
)
# Install from build directory so the generated QSS copy is included
INSTALL(
DIRECTORY
${PreferencePacks_Directories}
${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks/KindredCreate
DESTINATION
${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks
)

View File

@@ -25,6 +25,10 @@
<FCUInt Name="colorLogging" Value="2497893887" />
<FCUInt Name="colorWarning" Value="4192382975" />
<FCUInt Name="colorError" Value="4086016255" />
<FCBool Name="checkError" Value="1" />
<FCBool Name="checkLogging" Value="1" />
<FCBool Name="checkShowReportViewOnError" Value="1" />
<FCBool Name="checkShowReportViewOnWarning" Value="1" />
</FCParamGroup>
<FCParamGroup Name="View">
<FCUInt Name="BackgroundColor" Value="505294591" />
@@ -81,9 +85,29 @@
<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>
</FCParamGroup>
<FCParamGroup Name="MainWindow">
<FCText Name="StyleSheet">KindredCreate.qss</FCText>

File diff suppressed because it is too large Load Diff

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);
}