Compare commits
38 Commits
docs/updat
...
fix/missin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
098f0233c4 | ||
| 68690f3b22 | |||
| 087fe99324 | |||
| 7033744656 | |||
|
|
84c1776f92 | ||
| f391c526cd | |||
| ac4f01c5c0 | |||
| 1d2a11572d | |||
|
|
b3a6c9d925 | ||
| 7c84eeb68d | |||
|
|
590cb9f6c3 | ||
|
|
0a0ac547ef | ||
|
|
6773ca0dfd | ||
|
|
82e7362a56 | ||
| cf523f1d87 | |||
|
|
4bf74cf339 | ||
| 3b07a0f99b | |||
|
|
9cabb29824 | ||
| 793e6bdd49 | |||
| b6ab14cc6f | |||
| da46073d68 | |||
|
|
262dfa583d | ||
|
|
cc5ba638d1 | ||
|
|
35302154ae | ||
|
|
be7f1d9221 | ||
|
|
10b5c9d584 | ||
|
|
ee839c23b8 | ||
|
|
67f825c305 | ||
| 440df2a9be | |||
|
|
1750949afd | ||
| b2c6fc2ebc | |||
|
|
d0c67455dc | ||
|
|
62906b0269 | ||
| 99bc7629e7 | |||
|
|
d95c850b7b | ||
| 1f7dae4f11 | |||
| 1d7e4e2eef | |||
|
|
fe50b1595e |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
200
CONTRIBUTING.md
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
2
src/3rdParty/OndselSolver
vendored
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <a href="freecad:Std_AddonMgr">Addon Manager</a>.</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,6 +64,7 @@ private:
|
||||
void setProcessMessages();
|
||||
void setAutoSaving();
|
||||
void setToolBarIconSize();
|
||||
void setMenuIconSize();
|
||||
void setWheelEventFilter();
|
||||
void setLocale();
|
||||
void setCursorFlashing();
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
165
src/Mod/Create/update_checker.py
Normal 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"
|
||||
)
|
||||
1
src/Mod/Create/version.py.in
Normal file
@@ -0,0 +1 @@
|
||||
VERSION = "@KINDRED_CREATE_VERSION@"
|
||||
@@ -4,6 +4,7 @@
|
||||
add_executable(Gui_tests_run
|
||||
Assistant.cpp
|
||||
Camera.cpp
|
||||
OriginManager.cpp
|
||||
StyleParameters/StyleParametersApplicationTest.cpp
|
||||
StyleParameters/ParserTest.cpp
|
||||
StyleParameters/ParameterManagerTest.cpp
|
||||
|
||||
382
tests/src/Gui/OriginManager.cpp
Normal 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);
|
||||
}
|
||||