Compare commits

...

124 Commits

Author SHA1 Message Date
Chris Hennes 9fc7709969 Build: Update sign tool installation command in workflow
Cannot pin to a specific version since it is perpetually a "pre-release".
2026-03-18 14:41:41 -05:00
Kacper Donat 12cced51f1 Merge pull request #21978 from theo-vt/document_transaction
GSOC Multi file editing
2026-03-18 18:37:38 +01:00
Chris Hennes 439c50c5c4 Build: Pin Eigen to fix weekly build failure (#28421)
Pin version to >=3.3,<5 because of PCL incompatibility
2026-03-18 17:10:27 +00:00
Petter Reinholdtsen 87a3bcf9db Gui: Don't add redundant == or != to nullptr conditions
Follow C++ Core Guidelines ES.87: Don't add redundant == or != to conditions.
2026-03-18 11:15:45 -05:00
Benjamin Nauck 8910345149 Merge pull request #28033 from chennes/azureCodeSigning3
Packaging: Add Azure-based artifact signing on Win
2026-03-18 16:05:33 +01:00
PaddleStroke af4e54a6b7 Assembly: Fix datum typo in isBodySubObject (#28415) 2026-03-18 07:36:12 -05:00
wandererfan 90a0132533 [TD]fix selection order prevents area dimension 2026-03-17 14:18:03 -05:00
Chris Hennes fe425c3f57 Merge pull request #27747 from WandererFan/SmoothEdges
TechDraw: Draw smooth edges with thin line
2026-03-17 13:51:57 -05:00
dependabot[bot] 8098d72bb2 Bump step-security/harden-runner from 2.15.1 to 2.16.0
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.15.1 to 2.16.0.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](https://github.com/step-security/harden-runner/compare/58077d3c7e43986b6b15fba718e8ea69e387dfcc...fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 12:50:09 -05:00
dependabot[bot] ebde1e3d9a Bump actions/dependency-review-action from 4.8.2 to 4.8.3
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.8.2 to 4.8.3.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261...05fe4576374b728f0c523d6a13d64c25081e0803)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-version: 4.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 12:47:55 -05:00
dependabot[bot] d9b4006aa1 Bump geekyeggo/delete-artifact from 5.1.0 to 6.0.0
Bumps [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) from 5.1.0 to 6.0.0.
- [Release notes](https://github.com/geekyeggo/delete-artifact/releases)
- [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md)
- [Commits](https://github.com/geekyeggo/delete-artifact/compare/f275313e70c08f6120db482d7a6b98377786765b...176a747ab7e287e3ff4787bf8a148716375ca118)

---
updated-dependencies:
- dependency-name: geekyeggo/delete-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 12:43:27 -05:00
Chris Hennes 0b78b5ade8 Merge pull request #28247 from hyarion/various-text-fixes
Sketcher: Fix minor text tool issues
2026-03-17 11:44:40 -05:00
Benjamin Nauck 4750f06058 Build: Add harfbuzz to pixi.toml
It is already in pixi.lock
2026-03-17 11:28:13 -05:00
Chris Hennes 0cb8f7e240 Sketcher: Add backwards-compat path for external geo
Before 0aed23c (PR #19582), external geometry projection used a different algorithm and produced slightly different geometry for some kinds of projections (B-splines in particular). This adds a backwards-compatibility path that allows old files to continue to use the old algorithm, while using the new and improved algorithm with new files.
2026-03-17 15:22:11 +01:00
Chris Hennes 49d7d77274 PD: Add migration to handle reversed TwoLengths prism
Commit a346c266 (PR #21794) was a major refactoring of the extrusion code, and one consequence of it was that the 'TwoLengths' extrusion was deprecated and replaced. The code that replaced it generated the same final geometry, but in the opposite direction, changing the face order and breaking the element names. This commit adds a backwards-compatibility branch to the file loading code that flips the direction of the inputs, so the final output matches the original.
2026-03-17 02:06:42 +01:00
Bas Ruigrok d443d5b065 Gui: Combine getFocalPoint() and getCenterPointOnFocalPlane() 2026-03-16 23:10:11 +01:00
tarman3 013a1d5f3a CAM: Job Stock - Exclude PartDesign elements 2026-03-16 22:23:48 +01:00
Alfredo Monclus 180c39709a PartDesign: thread texture (#22573)
* PD: Hole Thread texture

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* refactor: vp-texture remove logging

* refactor: vp-hole cleanup unused declaration

* refactor: vp-hole remove logging

* refactor: vp-hole use convertTo

* refactor: vp-hole use freecad_cast

* refactor: make unwrapsurface private

* fix: logic error with model-thread handling

* fix: modelthreadchanged issues

threaddepth doesnt depend on modeled or not so remove it

* fix: model thread getting disabled when it shouldnt

* revert: use base-0 poly triangulation

previously had issues, but all is good now

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-16 21:52:36 +01:00
marioalexis 601bcd5e3d App: Fix separator path in safe mode 2026-03-16 21:03:28 +01:00
PaddleStroke dcb380e1ca Sketcher: Text position OVP fix 2026-03-16 19:15:09 +01:00
Alfredo Monclus cfe641fb7d Gui: improve quantitySpinbox / Sketcher OVP (#25978)
* Gui: fix quantity spinbox size not adapting to number of decimals

* Gui: fix quantity spinbox units getting cut (force max chars)

* Gui: editableDatumLabel remove hardcoded values

* fixes

* Gui: sketcher ovp auto adjust size

* Gui: ovp fix cursor jumping behind the unit

* Gui: Refactor changes to QuantitySpinBox

The QuantitySpinBox uses d-pointer (pimpl) to store its private data -
this commit ensures that all state is stored here and exposed via
getters and setters + qproperties.

* Gui: sketcher ovp fix cursor jumping after the unit

* Gui: allow quantitySpinBox to set max digits allowed

Useful for adjustable width in the Sketcher OVP

---------

Co-authored-by: Kacper Donat <kadet1090@gmail.com>
2026-03-16 18:41:00 +01:00
PaddleStroke d384c9d8cb Link : Fix transparent face regression 2026-03-16 18:32:36 +01:00
PaddleStroke 3620b68b21 Assembly: Command: Select joints of component (#27530)
* Assembly: Command: Select joints of component https://github.com/FreeCAD/FreeCAD/issues/23721

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update src/Mod/Assembly/Gui/Commands.cpp

Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
2026-03-16 18:25:05 +01:00
tarman3 20e70f84e1 CAM: ToolController - Default RampFeed after migration (#27191) 2026-03-16 13:10:34 -04:00
Nishendra Singh e28b1ce1ad techdraw: added supplementary angle display 2026-03-16 18:07:32 +01:00
Alfredo Monclus c8efc26982 PartDesign: fix the range of the custom clearance of threads (#26296)
* PartDesign: fix the range of the custom clearance of threads

* PartDesign: hole ensure diameter is on the correct range
2026-03-16 18:02:31 +01:00
Loke Strøm 9abe7b4af1 Sketcher: Fix: Arc of Hyperbola (#27656)
* Sketcher: Fix: Arc of Hyperbola

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added minor grater than major radius support

* Fixed merge errors

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Improved comments and added a confused points check for S1 and S2 distance.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added compile check for occ version and check before getting minor radius.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

OCC version check

Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
2026-03-16 17:49:34 +01:00
Timothy Miller 27f077c62b Part: Fix AttacherEngine property not synced with AttacherType (#15716)
When Part2DObject (e.g., Sketch) is created, it calls setAttacher() to
set up AttachEnginePlane. This updated the internal AttacherType property
but left the user-visible AttacherEngine enum at its default "Engine 3D".

Now setAttacher() also updates AttacherEngine for non-base attachers,
keeping the visible property in sync with the actual engine type.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-16 17:42:36 +01:00
PaddleStroke eabd052c17 Core: Fix freecad when windows path has non ASCII characters (#28222)
* Core: Fix freecad when Windows username has non ASCII characters

* Update ApplicationDirectories.cpp

* Tests: Add tests of pathToString/stringToPath

Validates the fix for non-ASCII characters in filesystem paths (e.g.
Windows usernames with umlauts). Includes Windows-specific tests that
verify pathToString produces UTF-8 from wide-string paths and that
the naive fs::path::string() does not. On Linux these functions are
effectively a no-op, so the tests themselves are only really useful
on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 17:37:43 +01:00
freecad-gh-actions-translation-bot 706b01f607 Update translations from Crowdin 2026-03-16 17:31:49 +01:00
dependabot[bot] 8a2f665430 Bump github/issue-metrics from 4.1.0 to 4.1.1
Bumps [github/issue-metrics](https://github.com/github/issue-metrics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/github/issue-metrics/releases)
- [Commits](https://github.com/github/issue-metrics/compare/41a7961f701cc64490f32e143af8ef479b93e87d...6a35322ff89cee3e1a594d282c27eb34bffa9174)

---
updated-dependencies:
- dependency-name: github/issue-metrics
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 14:05:28 +01:00
sliptonic 9fe630e0c3 Merge pull request #23093 from Connor9220/UpdateCircularHoleBaseGUI
CAM: Circular Hole Improvements - GUI sorting enhancements, and migration fixes
2026-03-15 15:02:28 -05:00
Ladislav Michl 91f015b132 Gui: {get,set}Camera expects utf-8 encoding
Python interface to the setCamera() is using 's' format string, which
converts Unicode object to C string using 'utf-8' encoding. Reflect
that for QString conversion as well.
2026-03-15 20:38:42 +01:00
theo-vt ad630cbb9c Use new transaction methods in dlgmaterial 2026-03-14 12:35:31 -04:00
theo-vt 51ac15b375 Allow one clipping plane dock widget per document 2026-03-14 12:15:47 -04:00
theo-vt db1469f219 Add dialog for link insertion when selection buffer is empty 2026-03-14 12:15:47 -04:00
theo-vt 1665dec4e4 Adress review 2026-03-14 12:15:47 -04:00
theo-vt 99e414efab Return string instead of const char* in Selection::getSelectedElement 2026-03-14 12:15:47 -04:00
theo-vt 7cb3815fb1 Filter events per document at the SelectionObserver level 2026-03-14 12:15:47 -04:00
theo-vt 5c6bf5461d Remove most activate-deactivate 2026-03-14 12:15:47 -04:00
theo-vt 3076ce66be Scope the selection per document 2026-03-14 12:15:47 -04:00
theo-vt d59471b6bd Gui: Allow a selection gate per document 2026-03-14 12:15:47 -04:00
theo-vt 4ba5504e7a Remove dupplicate clone code causing crash 2026-03-14 12:15:46 -04:00
theo-vt 7db3f901fd Fix diapearing sketcher tools 2026-03-14 12:15:46 -04:00
theo-vt f4665aa7b5 Core: support multiple active transactions 2026-03-14 12:15:46 -04:00
sliptonic 6d8b98d471 block delete logic 2026-03-14 11:19:14 -04:00
tarman3 03f506c5a5 fix onDocumentRestored 2026-03-14 11:18:54 -04:00
Turan Furkan Topak d90c88f0fc make polar and circular arrays aware of the active working plane (#28324) 2026-03-14 13:18:00 +01:00
Bojan Pejic df8c6b5db9 BIM: fix zero level height warning dialog (#28065)
* BIM: fix zero level height warning dialog
2026-03-14 11:48:49 +00:00
Yash Suthar 5455306a4a Draft: fix angular mode extension lines creation (#27987)
* Draft: fix angular mode extension lines creation

Signed-off-by: Yash Suthar <yashsuthar983@gmail.com>

* Draft: update the agularDimension extlines svg representation

Signed-off-by: Yash Suthar <yashsuthar983@gmail.com>

---------

Signed-off-by: Yash Suthar <yashsuthar983@gmail.com>
2026-03-14 12:34:58 +01:00
Furgo d1476c819c BIM: refactor link processing, add BIM_Link wrapper command (#28104)
* BIM: refactor, introduce link properties override helper

* BIM: add Link support to Arch_Add, Arch_Remove

Fixes: https://github.com/FreeCAD/FreeCAD/issues/26255
Fixes: https://github.com/FreeCAD/FreeCAD/issues/28139

Co-authored-by: Roy-043 <70520633+Roy-043@users.noreply.github.com>

* BIM: add link command wrapper

* BIM: add link addition and removal tests

---------

Co-authored-by: Roy-043 <70520633+Roy-043@users.noreply.github.com>
2026-03-14 12:22:02 +01:00
Billy Huddleston 88a74cfa3a Fix Drag'n drop for QT6.10
Fix Merge conflicts
2026-03-13 18:09:12 -04:00
Billy Huddleston 5e818e961c CAM: Remove "Automatic TSP" mode and improve natural sorting
CAM: CircularHoleBase - Fix onDocumentRestored

Removed the expermental "Automatic TSP" sorting mode and ensured legacy
files are migrated to "Automatic". Start using C++ TSP sorting for
"Automatic" mode for better performance. Refactored natural sorting for
table columns, relocating the utility class for reuse and using a robust
sort key.

src/Mod/CAM/Path/Op/CircularHoleBase.py:
- Removed "Automatic TSP" from SortingMode options and logic
- Added migration logic for legacy "Automatic TSP" values
- Cleaned up redundant assignments

src/Mod/CAM/Path/Op/Gui/CircularHoleBase.py:
- Updated to use relocated NaturalSortTableWidgetItem for feature and
diameter columns
- Improved sorting behavior for mixed text/number columns

src/Mod/CAM/Path/Base/Gui/Util.py:
- Added NaturalSortTableWidgetItem using the existing natural_sort_key
for robust sorting
2026-03-13 15:57:13 -04:00
Billy Huddleston fdef6c35e7 GUI sorting enhancements, and migration fixes
This update enhances the GUI with new sorting modes and manual reordering, and
adds migration logic to ensure robust property

src/Mod/CAM/Gui/Resources/panels/PageBaseHoleGeometryEdit.ui:
- Add Sorting mode combo box with "Automatic", "Automatic TSP", and "Manual" options
- Add UI improvements for hole sorting, selection, and filtering

src/Mod/CAM/Path/Op/CircularHoleBase.py:
- Add migration logic to ensure SortingMode property exists and is up to date
- Use sort_locations_tsp when SortingMode is "Automatic TSP"

src/Mod/CAM/Path/Op/Gui/CircularHoleBase.py:
- Add RowMoveTableWidget for manual row reordering in the GUI
- Update TaskPanelHoleGeometryPage to support new sorting modes and improved UI behavior
2026-03-13 15:56:14 -04:00
Chris Hennes 4ac848aab0 Merge pull request #26031 from pjcreath/split-sketchobject
Split monolithic SketchObject.cpp
2026-03-13 14:37:49 -05:00
sliptonic e1fdc27e7c Merge pull request #28321 from davidgilkaufman/roadmap_clipper
[CAM] Clipper upgrade roadmap epic
2026-03-13 13:35:07 -05:00
sliptonic 0caccde398 Merge pull request #25667 from tarman3/boundary_threshold
CAM: DressupBoundary - RetractThreshold
2026-03-13 12:52:59 -05:00
sliptonic 244dffcfb7 Merge pull request #27514 from tarman3/holes_diameter
CAM: CircularHoleBase - Fix holeDiameter() for face
2026-03-13 12:45:43 -05:00
David Kaufman 5328e74e3d link FPA grant proposal 2026-03-13 13:27:54 -04:00
sliptonic 3c48b4a950 CAM: Machine-based postprocessing (#27507)
CAM: Add template dropdown below machine name in editor

Adds a template selection dropdown to the Machine Editor dialog,
positioned below the machine name field. Dropdown lists user and
built-in templates for new machines only. Adds MachineFactory
template discovery and ensures templates are copied to build directory.

src/Mod/CAM/CMakeLists.txt:
- Copy machine templates to build directory for runtime access

src/Mod/CAM/Machine/models/machine.py:
- Add MachineFactory.list_builtin_templates() for template discovery
- Use FreeCAD install path for built-in templates

src/Mod/CAM/Machine/ui/editor/machine_editor.py:
- Add template dropdown below name field for new machines
- Populate dropdown with user and built-in templates
- Update UI fields when template is selected

Major improvements to the GenericPlasma postprocessor with new dialog-based
mode selection and robust plasma cutting features.

New Features:
- Add pre_processing_dialog() for interactive mode selection
- Replace persistent mark_entry_only property with runtime dialog
- User-friendly choice between Normal Cutting and Mark Entry Points Only
- Enhanced torch control with pierce height movement
- Complete mark_entry_only implementation with proper marking sequence
- Improved error handling and state management

Technical Improvements:
- Add _reset_plasma_state() for clean per-operation tracking
- Fix Z direction detection with proper null checking
- Enhanced height extraction from StartDepth/FinalDepth/ClearanceHeight
- Better error handling with debug logging
- Graceful fallback when GUI not available

Bug Fixes:
- Fix torch control to move to pierce height before ignition
- Fix force_rapid_feeds to remove F parameters (revert from override)
- Fix state tracking initialization and null value handling
- Add proper state reset for each operationUses a dialog to set mark_only.

Co-authored-by: tarman3 <joice501790018@tutamail.com>
2026-03-13 13:18:45 -04:00
sliptonic a78c31be69 Merge pull request #28274 from Connor9220/FixToolbitSubClass
CAM: Fix saving of _shape_type for toolbits
2026-03-13 12:11:19 -05:00
sliptonic c2495411b7 Merge pull request #28173 from tarman3/linking_opti
CAM: Linking - Minor optimization for simple solid
2026-03-13 12:01:50 -05:00
sliptonic 1a8e2fb333 Merge pull request #28172 from tarman3/maneuver_toCommand
CAM: Maneuver - Move instruction_to_command() to class
2026-03-13 12:00:07 -05:00
sliptonic 73c3c78474 Merge pull request #28167 from tarman3/machinestate
CAM: MachineState - Allow process list of commands
2026-03-13 11:57:49 -05:00
sliptonic 509c881ea0 Merge pull request #28140 from tarman3/linking_tolerance
CAM: Linking - Tolerance in get_linking_moves()
2026-03-13 11:55:08 -05:00
David Kaufman d690dcb1d3 Clipper upgrade roadmap 2026-03-13 11:47:33 -04:00
dependabot[bot] 493829b376 Bump actions/download-artifact from 8.0.0 to 8.0.1
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-12 21:46:11 -05:00
pre-commit-ci[bot] 0819d776d7 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-03-12 20:25:01 +00:00
pjcreath 79aca8950f Sketch: [5/5] Fix up files after splitting SketchObject.cpp
Remove stale includes and make other adjustments for compilation.
2026-03-12 16:17:08 -04:00
pjcreath 8efe9964e7 Sketch: [4/5] Move code from SketchObject.cpp to SketchObjectExternal.cpp.
This is a purely mechanical reorganization with no functional changes.
Use "git blame -C --minimal" to trace line origins prior to this commit.
2026-03-12 16:17:08 -04:00
pjcreath eda87a7efe Sketch: [3/5] Move code from SketchObject.cpp to SketchObjectGeometry.cpp.
This is a purely mechanical reorganization with no functional changes.
Use "git blame -C --minimal" to trace line origins prior to this commit.
2026-03-12 16:17:08 -04:00
pjcreath e1815f10ff Sketch: [2/5] Move code from SketchObject.cpp to SketchObjectConstraints.cpp.
This is a purely mechanical reorganization with no functional changes.
Use "git blame -C --minimal" to trace line origins prior to this commit.
2026-03-12 16:17:08 -04:00
pjcreath 7d39a9d59a Sketch: [1/5] Move code from SketchObject.cpp to SketchObjectOperations.cpp.
This is a purely mechanical reorganization with no functional changes.
Use "git blame -C --minimal" to trace line origins prior to this commit.
2026-03-12 16:17:08 -04:00
wmayer baa47bd606 Sketch: Refactor SketchObject::getPoint
Add the helper class SketchGeometryType and the interface SketchGeometry with the needed sub-classes to
remove the cascaded if-else construct
2026-03-12 16:17:08 -04:00
wmayer 51b37da53e Sketch: Fix crash when moving sketch with hidden constraints
Clicking the eye icon on the constraints panel hides all constraints. This calls SketchObject::setVirtualSpace where the existing Constraint
objects will be replaced. However, the Constraint objects are also stored inside the member 'solvedSketch' but won't be replaced so that the
pointers there will become dangling.

If the user starts to drag the sketch the pointers are still dangling and cause a crash when calling 'solvedSketch.moveGeometries'.

The crash can be reproduced with:
1. Create a sketch
2. Add a single line
3. Set a length constraint
4. Make it a non-driving constraint
5. Hide it with the eye icon or by unticking the check box
6. Move the line

The same crash also occurs when calling the methods setLabelDistance() or setLabelPosition() from Python and then trying to move the sketch.

Solution:

To fix the problem add the lightweight method 'Sketch::updateConstraints' and call it inside SketchObject::setVirtualSpace to quickly update
the pointers after invalidating them.

This fixes issue 19566
2026-03-12 16:17:08 -04:00
André Lopes 8ce3c4a1cc Fix #24079: clarify TechDraw Scale tooltip (#28293) 2026-03-12 18:12:02 +00:00
Roy-043 037ce3bffd BIM: update tooltips in BIM Setup to match current prefs
Related to #25004

Updated the tooltips to point to correct pref pages.

Additionally:
* Minor text tweaks.
* Replaced "Gui::PrefColorButton" with "Gui::ColorButton".
2026-03-12 18:28:48 +01:00
Roy-043 b9306c6f63 BIM: fix BuildingPart color issues including v1.0 regressions (#28001)
* BIM: fix BuildingPart color issues including v1.0 regressions

Fixes #27136.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-12 14:39:24 +00:00
tarman3 7b2b972efa CAM: MachineState - Allow process list of commands 2026-03-12 10:28:31 +02:00
filippor 6d66721582 fix CAM missing python file in cmake 2026-03-11 22:03:23 -04:00
Billy Huddleston 448235cd2b CAM: Fix saving of _shape_type for toolbits
Corrects logic so that the _shape_type is saved properly and not always
defaulted to the primary toolbit type. Ensures subclass/alias
information is preserved when saving toolbits.

src/Mod/CAM/Path/Tool/toolbit/models/base.py:
- Fix fallback logic for _shape_type to correctly save subclass/alias
when present
- Only default to primary type if _shape_type is missing or matches the
main class

src/Mod/CAM/Path/Tool/docobject/ui/docobject.py:
- Improve label formatting by handling underscores, hyphens, and extra
spaces

src/Mod/CAM/Path/Tool/toolbit/ui/typefilter.py:
- Simplify type filter mapping and display logic for toolbit subtypes
2026-03-11 21:58:32 -04:00
Bojan a729bb4bc1 Material: add transaction support to Std_SetMaterial task 2026-03-11 20:00:39 -05:00
Chris Hennes 03fc08d02e Revert "[Weekly Build] Update IfcOpenShell to 0.8.4"
This reverts commit 5e7bbe15e3.
2026-03-11 08:18:44 +01:00
WandererFan e44c1ce75a TechDraw: Allow 3d dimension references to use point objects (#28199)
* [TD]fix 3d point object reference in dimension

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-11 06:08:35 +01:00
marioalexis 4083c7c616 Fem: Add name to examples dock widget 2026-03-11 06:05:07 +01:00
PaddleStroke 76a84f63ab Sketcher: Dimension tool: don't update distance type based on position for horizontal/vert 2026-03-11 05:36:10 +01:00
WandererFan 065315d7a4 TechDraw: fix crash in area dim for face with nested voids (#28097) 2026-03-10 22:57:59 -05:00
Chris Hennes 6206ed2c51 Packaging: Add Azure-based artifact signing on Win
Sign all *.exe, *.dll, and *.pyd files in the bin directory of the
build, as well as the two Chocolatey stubs and the installer. Requires
the use of several environment variables and secrets, documented at
https://github.com/FreeCAD/DevelopersHandbook/blob/main/technical/codesigning.md#windows
2026-03-10 22:25:02 -05:00
PaddleStroke 451072f0d7 Sketcher : arc with symmetric + 2 coincidence fix (#28120) 2026-03-10 22:13:02 -05:00
Roy-043 0b232ac1ff Draft: Avoid import of whole Draft module (#25386) 2026-03-10 21:59:36 -05:00
Grzegorz Kwacz 737cab44f2 Gui/View: Move (get|set)Camera to methods (#27801) 2026-03-10 21:57:34 -05:00
Benjamin Nauck a06be5de68 Sketcher: Fix void bounding box fall-through in constraint visualization
Add missing break after hiding rectangle points for a void bounding box,
preventing fall-through to CornerMin()/CornerMax() which would produce
undefined values.
2026-03-10 19:11:48 +01:00
Benjamin Nauck 0ea238f90c Sketcher: Fix dangling command context on double-click text edit
Remove the openCommand() call in editDoubleClicked() since
EditTextDialog already manages its own command lifecycle. The redundant
outer command was never closed when the user cancelled the dialog.
2026-03-10 19:11:48 +01:00
Benjamin Nauck 5ae85148cc Merge pull request #22217 from PaddleStroke/sk_texttool
Sketcher: Text tool
2026-03-10 18:07:50 +01:00
tarman3 1182be91bc CAM: Maneuver - Move instruction_to_command() to class 2026-03-10 15:52:35 +02:00
Benjamin Nauck cd5876dc54 Sketcher: Escape text and font path in EditTextDialog
Escape user-provided text and font path strings before inserting them
into the Python command string, preventing breakage or injection from
strings containing quotes or backslashes.
2026-03-10 12:54:45 +01:00
Benjamin Nauck ca4660167e Sketcher: Add .ttc (TrueType Collection) to font file discovery 2026-03-10 12:54:45 +01:00
Benjamin Nauck a0c97a102e Part: Add HarfBuzz text shaping for proper kerning in makeTextWires
Replace the character-by-character positioning loop with HarfBuzz
text shaping. The old code used FT_Get_Kerning which only reads
legacy kern tables. HarfBuzz handles both GPOS kerning and legacy
kern tables, as well as ligatures and complex script shaping.
2026-03-10 12:54:45 +01:00
Roy-043 90c83636cb Draft: update GUI text in Draft_SelectPlane
After #27979, the Draft_SelectPlane code also accepts 1 or 2 vertices. This PR updates the task panel text accordingly.
2026-03-10 10:33:23 +01:00
tarman3 e561950ff8 CAM: Linking - Minor optimization for simple solid 2026-03-10 09:06:14 +02:00
PaddleStroke e7e5e39e7b Sketcher: Change default text to Text 2026-03-10 07:22:26 +01:00
paddle d842e4151c Sketcher: Text: Fixes 2026-03-10 07:22:25 +01:00
paddle 320bd61156 Sketcher: Text tool - review changes 2026-03-10 07:22:25 +01:00
PaddleStroke fed11359d9 Update SketcherToolDefaultWidget.h 2026-03-10 07:22:09 +01:00
paddle fdf0013dcb Sketcher: Text tool 2026-03-10 07:21:04 +01:00
tarman3 bf98221885 CAM: Linking - Tolerance in get_linking_moves() 2026-03-10 06:52:28 +02:00
Bojan d214306d1b PartDesign/Part: prevent chamfer crash on OCC kernel failure 2026-03-10 01:52:57 +01:00
PaddleStroke a0af0d2b85 Link: Fix mirror twice color issue (#28071) 2026-03-09 22:12:34 +01:00
Turan Furkan Topak ee27de857a Contribution Standards: Adding new rules, mostly around AI discussions (#24646) 2026-03-09 15:55:49 -05:00
sliptonic 4afbadec04 Merge pull request #26605 from tarman3/inspect_title
CAM: Inspect and Export - Window title
2026-03-09 15:25:18 -05:00
sliptonic cdd9d685fe CAM: Improvements to machine (#28011)
* CAM:  Improvements to machine

* CAM: SPDX

* lint and translation string problems

* fixing headers and missing toolhead tests

---------

Co-authored-by: tarman3 <joice501790018@tutamail.com>
2026-03-09 14:02:31 -04:00
Krish Sharma a34bad7da4 Add status bar hints for modifier keys in Std_Measure (#26568)
* Add status bar hints for modifier keys in Std_Measure

* Align status bar hints according to design guide

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update src/Mod/Measure/Gui/TaskMeasure.cpp

Co-authored-by: Max Wilfinger <6246609+maxwxyz@users.noreply.github.com>

* Update src/Mod/Measure/Gui/TaskMeasure.cpp

Co-authored-by: Max Wilfinger <6246609+maxwxyz@users.noreply.github.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Replace 'invert' with 'toggle' to improve readability

* Added missing namespace qualifier

* Fix MSVC list assignment errors with explicit std::list<InputHint>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix InputHint': undeclared identifier

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Max Wilfinger <6246609+maxwxyz@users.noreply.github.com>
2026-03-09 18:24:54 +01:00
PaddleStroke e20b42e484 Assembly: fix joint creation not working on draft links (#26085)
* Assembly: add support for draft links

* Update AssemblyObject.cpp

* Update ViewProviderAssembly.cpp

* Update GeoFeature.cpp

* Update JointObject.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-09 18:15:01 +01:00
PaddleStroke 79923c16f3 Gui: Fix shortcut warning (#28217) 2026-03-09 18:00:22 +01:00
Bojan 004927e16e TechDraw: clear selection after editing Annotation from 2D page Fixes FreeCAD#27983 2026-03-09 17:59:17 +01:00
Yorik van Havre e8d9f30c10 Merge pull request #28020 from Roy-043/BIM-remove-switch-to-beam-creation-based-on-dimensions
BIM: remove switch to profile beam creation based on dimensions
2026-03-09 16:52:18 +00:00
tarman3 f7939beee0 CAM: Linking - Tolerance in get_linking_moves() 2026-03-08 21:30:24 +02:00
tarman3 0702616895 CAM: BoundaryDressup - RetractThreshold 2026-03-08 12:47:43 +02:00
tarman3 26357db694 CAM: Inspect and Export gcode window title 2026-03-06 21:46:58 +02:00
Roy-043 64a4cee116 BIM: remove switch to profile beam creation based on dimensions
Fixes #28013.

When creating a structure from a profile the code could ignore `self.bmode` (beam-mode) and create a beam even if the user selected the column option in the task panel.

Rounding errors due to the conversion of numerical values read from the input boxes triggered the bug (89mm -> 3.5" -> 88.89999999999999mm).

But beyond that, it is IMO not correct to disregard user input. If the column option is specified, a column should be created irrespective of the dimensions.
2026-03-02 21:17:56 +01:00
tarman3 eb0d7ba6a5 CAM: CircularHoleBase.holeDiameter() - Remove useless obj 2026-02-27 20:43:56 +02:00
tarman3 eaf76e4d0a CAM: CircularHoleBase.holeDiameter() - Fix face 2026-02-27 20:41:07 +02:00
wandererfan ba24ab8a49 [TD]fix unhelpful warning message 2026-02-19 15:30:48 -05:00
wandererfan b0e11e286b [TD]use thin line for smooth edges 2026-02-19 15:30:48 -05:00
646 changed files with 83265 additions and 55418 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
logdir: /tmp/log/
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+78 -14
View File
@@ -10,6 +10,10 @@ permissions:
contents: write
actions: write
concurrency:
group: build-release-${{ github.event_name }}
cancel-in-progress: true
jobs:
upload_src:
runs-on: ubuntu-latest
@@ -17,11 +21,11 @@ jobs:
build_tag: ${{ steps.get_tag.outputs.build_tag }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- name: Checkout Source
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.sha }}
@@ -29,7 +33,7 @@ jobs:
fetch-tags: true
submodules: 'recursive'
- name: get tag and create release if weekly
- name: Get tag and create release if weekly
id: get_tag
shell: bash -l {0}
env:
@@ -39,7 +43,10 @@ jobs:
export BUILD_TAG="${{ github.event.release.tag_name }}"
else
export BUILD_TAG=weekly-$(date "+%Y.%m.%d")
gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" -F .github/workflows/weekly-build-notes.md --prerelease || true
gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" \
-F .github/workflows/weekly-build-notes.md \
--prerelease || \
gh release view ${BUILD_TAG} > /dev/null # fail if it doesn't exist
fi
echo "BUILD_TAG=${BUILD_TAG}" >> "$GITHUB_ENV"
echo "build_tag=${BUILD_TAG}" >> "$GITHUB_OUTPUT"
@@ -105,7 +112,7 @@ jobs:
environment: weekly-build
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
@@ -118,7 +125,7 @@ jobs:
remove-android: 'true' # (frees ~9 GB)
remove-cached-tools: 'true' # (frees ~8.3 GB)
- name: Set Platform Environment Variables
- name: Set platform environment variables
shell: bash -l {0}
env:
OPERATING_SYSTEM: ${{ runner.os }}
@@ -128,7 +135,7 @@ jobs:
echo 'RATTLER_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV"
fi
- name: Checkout Source
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.sha }}
@@ -142,7 +149,7 @@ jobs:
cache: false
- name: Install the Apple certificate and provisioning profile
id: get_cert
id: macos_get_cert
if: runner.os == 'macOS'
env:
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
@@ -150,7 +157,6 @@ jobs:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
DEVELOPER_TEAM_ID: ${{ secrets.DEVELOPER_TEAM_ID }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then
@@ -186,23 +192,81 @@ jobs:
xcrun notarytool store-credentials "FreeCAD" --keychain "$KEYCHAIN_PATH" --apple-id "${APPLE_ID}" --password "${APP_SPECIFIC_PASSWORD}" --team-id "${DEVELOPER_TEAM_ID}"
- name: Build and Release Packages
- name: Setup .NET 10 SDK
if: runner.os == 'Windows'
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with:
dotnet-version: '10.0.103'
- name: Install sign tool
if: runner.os == 'Windows'
run: |
# NOTE: The sign tool is ONLY available as a prerelease package, they never issue real releases.
dotnet tool install --global --prerelease sign
echo "$env:USERPROFILE\.dotnet\tools" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
dotnet --info
sign --version
- name: Build packages
shell: bash
env:
GH_TOKEN: ${{ github.token }}
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGN_RELEASE: ${{ steps.get_cert.outputs.has_cert }}
TARGET_PLATFORM: ${{ matrix.target }}
MAKE_INSTALLER: "true"
UPLOAD_RELEASE: "true"
BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }}
run: |
set -euo pipefail
if [[ "${{ runner.os }}" == "macOS" ]]; then
export MACOS_DEPLOYMENT_TARGET="${{ matrix.deploy_target }}"
fi
python3 package/scripts/write_version_info.py ../freecad_version.txt
cd package/rattler-build
pixi install
- name: Azure login for Windows build code signing
id: azure_login
if: runner.os == 'Windows'
continue-on-error: true
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}'
- name: Release packages with optional code-signing on Windows and macOS
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TARGET_PLATFORM: ${{ matrix.target }}
MAKE_INSTALLER: "true"
UPLOAD_RELEASE: "true"
BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }}
run: |
set -euo pipefail
export MACOS_SIGN_RELEASE=0
export WINDOWS_SIGN_RELEASE=0
case "${RUNNER_OS}" in
Windows)
export WINDOWS_SIGN_RELEASE="${{ steps.azure_login.outcome }}"
export WINDOWS_AZURE_ENDPOINT="${{ vars.AZURE_TRUSTED_SIGNING_ENDPOINT }}"
export WINDOWS_AZURE_CERTIFICATE_PROFILE="${{ vars.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}"
export WINDOWS_AZURE_SIGNING_ACCOUNT="${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT }}"
# For good measure, normalize the azure_login result to 1 or 0
if [[ "${WINDOWS_SIGN_RELEASE:-}" == "success" ]]; then
export WINDOWS_SIGN_RELEASE=1
else
export WINDOWS_SIGN_RELEASE=0
fi
;;
macOS)
export MACOS_SIGN_RELEASE="${{ steps.macos_get_cert.outputs.has_cert }}"
export MACOS_SIGNING_KEY_ID="${{ secrets.SIGNING_KEY_ID }}"
export MACOS_DEPLOYMENT_TARGET="${{ matrix.deploy_target }}"
;;
esac
cd package/rattler-build
pixi run -e package create_bundle
## Needed if running on a self-hosted runner:
+1 -1
View File
@@ -64,7 +64,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+1 -1
View File
@@ -68,7 +68,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+2 -2
View File
@@ -17,11 +17,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: 'Dependency Review'
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
@@ -35,7 +35,7 @@ jobs:
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@41a7961f701cc64490f32e143af8ef479b93e87d # v4.1.0
uses: github/issue-metrics@6a35322ff89cee3e1a594d282c27eb34bffa9174 # v4.1.1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:FreeCAD/FreeCAD is:issue created:${{ env.last_month }}'
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+1 -1
View File
@@ -75,7 +75,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+1 -1
View File
@@ -77,7 +77,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+1 -1
View File
@@ -78,7 +78,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+1 -1
View File
@@ -198,7 +198,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+1 -1
View File
@@ -81,7 +81,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
+3 -3
View File
@@ -54,7 +54,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
@@ -62,7 +62,7 @@ jobs:
run: |
mkdir -p ${{ env.artifactsDownloadDir }}
- name: Download artifacts
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: ${{ env.artifactsDownloadDir }}
- name: Save input data to file
@@ -119,7 +119,7 @@ jobs:
cat report.md >> $GITHUB_STEP_SUMMARY
- name: Delete used artifacts
continue-on-error: true
uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5.1.0
uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
with:
name: |
${{ env.usedArtifacts }}
+3 -1
View File
@@ -62,7 +62,9 @@ The FreeCAD Contribution Process is expressed here with the following specific g
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.
14. Contributions must meet existing quality standards. Raw AI output is not accepted under any circumstances. AI may be used only as an assistive tool; in all cases, the resulting content must be reviewed, validated, and justifiable by the contributor. The contributor should be able to explain design and code decisions, answer reviewers questions, and ensure that AI use does not waste reviewers time during review. That being said, the use of AI is not recommended under any circumstances or in any manner.
15. The contributor provides reasonable assurance that the contribution does not infringe third-party copyrights or license terms.
16. A “Valid PR” is one which satisfies the above requirements.
## 6. Process
+16
View File
@@ -7,6 +7,22 @@ macro(SetupFreetype)
message("===============================================================\n"
"FreeType2 not found. Part module will lack of makeWireString().\n"
"===============================================================\n")
else()
# find_package(harfbuzz CONFIG) fails on windows
# lets do it the complicated way instead.
find_path(HARFBUZZ_INCLUDE_DIR hb.h PATH_SUFFIXES harfbuzz)
find_library(HARFBUZZ_LIBRARY NAMES harfbuzz)
if(HARFBUZZ_INCLUDE_DIR AND HARFBUZZ_LIBRARY)
if(NOT TARGET harfbuzz::harfbuzz)
add_library(harfbuzz::harfbuzz UNKNOWN IMPORTED)
set_target_properties(harfbuzz::harfbuzz PROPERTIES
IMPORTED_LOCATION "${HARFBUZZ_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${HARFBUZZ_INCLUDE_DIR}"
)
endif()
else()
message(FATAL_ERROR "HarfBuzz not found")
endif()
endif(NOT FREETYPE_FOUND)
endif(FREECAD_USE_FREETYPE)
+2 -2
View File
@@ -76,9 +76,9 @@ if [ -d "${conda_env}/PlugIns" ]; then
mv ${conda_env}/PlugIns ${conda_env}/..
fi
if [[ "${SIGN_RELEASE}" == "true" ]]; then
if [[ "${MACOS_SIGN_RELEASE}" == "true" ]]; then
# create the signed dmg
../../scripts/macos_sign_and_notarize.zsh -p "FreeCAD" -k ${SIGNING_KEY_ID} -o "${version_name}.dmg"
../../scripts/macos_sign_and_notarize.zsh -p "FreeCAD" -k ${MACOS_SIGNING_KEY_ID} -o "${version_name}.dmg"
else
# Ad-hoc sign for local builds (required for QuickLook extensions to register)
if [ -d "FreeCAD.app/Contents/PlugIns" ]; then
+10 -10
View File
@@ -90,7 +90,7 @@ environments:
- conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/linux-64/icu-75.1-he02047a_0.conda
- conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/linux-64/ifcopenshell-0.8.4-py311hfa9dd64_2.conda
- conda: https://prefix.dev/conda-forge/linux-64/ifcopenshell-0.8.2-py311hfea35e5_0.conda
- conda: https://prefix.dev/conda-forge/linux-64/imath-3.1.12-h7955e40_0.conda
- conda: https://prefix.dev/conda-forge/linux-64/jasper-4.2.8-he3c4edf_0.conda
- conda: https://prefix.dev/conda-forge/linux-64/jsoncpp-1.9.6-hf42df4d_1.conda
@@ -422,7 +422,7 @@ environments:
- conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/linux-aarch64/icu-75.1-hf9b3779_0.conda
- conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/linux-aarch64/ifcopenshell-0.8.4-py311h9ffcc0b_2.conda
- conda: https://prefix.dev/conda-forge/linux-aarch64/ifcopenshell-0.8.2-py311h9a0e4cb_0.conda
- conda: https://prefix.dev/conda-forge/linux-aarch64/imath-3.1.12-hf428078_0.conda
- conda: https://prefix.dev/conda-forge/linux-aarch64/jasper-4.2.8-h27a9ab5_0.conda
- conda: https://prefix.dev/conda-forge/linux-aarch64/jsoncpp-1.9.6-h34915d9_1.conda
@@ -741,7 +741,7 @@ environments:
- conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda
- conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/osx-64/ifcopenshell-0.8.4-py311hbba92a7_2.conda
- conda: https://prefix.dev/conda-forge/osx-64/ifcopenshell-0.8.2-py311h2fdbcd7_0.conda
- conda: https://prefix.dev/conda-forge/osx-64/imath-3.1.12-h2016aa1_0.conda
- conda: https://prefix.dev/conda-forge/osx-64/jasper-4.2.8-h9ce442b_0.conda
- conda: https://prefix.dev/conda-forge/osx-64/jsoncpp-1.9.6-h466cfd8_1.conda
@@ -1011,7 +1011,7 @@ environments:
- conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda
- conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/osx-arm64/ifcopenshell-0.8.4-py311h5dd451c_2.conda
- conda: https://prefix.dev/conda-forge/osx-arm64/ifcopenshell-0.8.2-py311h30f7335_0.conda
- conda: https://prefix.dev/conda-forge/osx-arm64/imath-3.1.12-h025cafa_0.conda
- conda: https://prefix.dev/conda-forge/osx-arm64/jasper-4.2.8-hc0e5025_0.conda
- conda: https://prefix.dev/conda-forge/osx-arm64/jsoncpp-1.9.6-h726d253_1.conda
@@ -1274,7 +1274,7 @@ environments:
- conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/win-64/icu-75.1-he0c23c2_0.conda
- conda: https://prefix.dev/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda
- conda: https://prefix.dev/conda-forge/win-64/ifcopenshell-0.8.4-py311h5252d0d_2.conda
- conda: https://prefix.dev/conda-forge/win-64/ifcopenshell-0.8.2-py311ha1ff2f0_0.conda
- conda: https://prefix.dev/conda-forge/win-64/imath-3.1.12-hbb528cf_0.conda
- conda: https://prefix.dev/conda-forge/win-64/jasper-4.2.8-h8ad263b_0.conda
- conda: https://prefix.dev/conda-forge/win-64/jsoncpp-1.9.6-hda1637e_1.conda
@@ -6121,7 +6121,7 @@ packages:
license_family: BSD
size: 50721
timestamp: 1760286526795
- conda: https://prefix.dev/conda-forge/linux-64/ifcopenshell-0.8.4-py311hfa9dd64_2.conda
- conda: https://prefix.dev/conda-forge/linux-64/ifcopenshell-0.8.2-py311hfea35e5_0.conda
sha256: 45ce07c412cb1d4b8bfd0fde785e9f3751381b4e3d6a28686a174559f0628e69
md5: 25bcf0826fc3b896d07afbe35982a500
depends:
@@ -6145,7 +6145,7 @@ packages:
license_family: LGPL
size: 43852878
timestamp: 1745412691720
- conda: https://prefix.dev/conda-forge/linux-aarch64/ifcopenshell-0.8.4-py311h9ffcc0b_2.conda
- conda: https://prefix.dev/conda-forge/linux-aarch64/ifcopenshell-0.8.2-py311h9a0e4cb_0.conda
sha256: 5b0d7760899ffa8ae91169bff49c9d6cd435b1cb8957e7622d9bd765a9c2d577
md5: fc1ebe0deebc32d2d42b8dc257afb836
depends:
@@ -6168,7 +6168,7 @@ packages:
license_family: LGPL
size: 46059599
timestamp: 1745412697613
- conda: https://prefix.dev/conda-forge/osx-64/ifcopenshell-0.8.4-py311hbba92a7_2.conda
- conda: https://prefix.dev/conda-forge/osx-64/ifcopenshell-0.8.2-py311h2fdbcd7_0.conda
sha256: d52f4bd7cec3cf1cbcd7cf9c6a21ec6d2331425972c4cd7f88d3df64a4a3cd72
md5: d0352d59f66ec49a974177e1efc749df
depends:
@@ -6190,7 +6190,7 @@ packages:
license_family: LGPL
size: 39452121
timestamp: 1745412590610
- conda: https://prefix.dev/conda-forge/osx-arm64/ifcopenshell-0.8.4-py311h5dd451c_2.conda
- conda: https://prefix.dev/conda-forge/osx-arm64/ifcopenshell-0.8.2-py311h30f7335_0.conda
sha256: 15d5d080aa54c1b5aeed0e4827d5a6b4521b8a2599e3bc06af5318575d2b7a8f
md5: b550b7b3c4a8f543fb3c0c15f8ec5967
depends:
@@ -6213,7 +6213,7 @@ packages:
license_family: LGPL
size: 40077397
timestamp: 1745412613454
- conda: https://prefix.dev/conda-forge/win-64/ifcopenshell-0.8.4-py311h5252d0d_2.conda
- conda: https://prefix.dev/conda-forge/win-64/ifcopenshell-0.8.2-py311ha1ff2f0_0.conda
sha256: 4e3c4d0d3749b761267b9ac9f9c3262c2b690d1908a6a6db734dcd4eecd43b2e
md5: e178c988f7034d08d27fe14a62bdbe12
depends:
+1 -1
View File
@@ -100,7 +100,7 @@ requirements:
host:
- coin3d
- eigen
- eigen >=3.3,<5
- fmt
- freetype
- hdf5
@@ -44,6 +44,9 @@ find ${copy_dir} -name \*arm\*.exe -delete # arm binaries that fail to extract u
mv ${copy_dir}/bin/Lib/ssl.py .ssl-orig.py
cp ssl-patch.py ${copy_dir}/bin/Lib/ssl.py
# Turn off the echo before we start actually calling "echo"
set +x
echo '[Paths]' >> ${copy_dir}/bin/qt6.conf
echo 'Prefix = ../lib/qt6' >> ${copy_dir}/bin/qt6.conf
@@ -67,6 +70,64 @@ sed -i '1s/.*/\nLIST OF PACKAGES:/' ${copy_dir}/packages.txt
mv ${copy_dir} ${version_name}
# Sign the EXE, DLL, and PYD files (if we can access the Azure account for signing):
set -euo pipefail
SIGN_DIR="${version_name}"
if [[ "${WINDOWS_SIGN_RELEASE:-0}" == "1" ]]; then
TENANT="$(az account show --query tenantId -o tsv)"
export AZURE_IDENTITY_DISABLE_WORKLOAD_IDENTITY=true
export AZURE_IDENTITY_DISABLE_MANAGED_IDENTITY=true
unset AZURE_IDENTITY_LOGGING_ENABLED
if az account get-access-token \
--tenant "$TENANT" \
--scope "https://codesigning.azure.net/.default" \
>/dev/null 2>&1;
then
echo "Azure Artifact Signing access confirmed. Beginning signing process..."
shopt -s nullglob
FILES=(
"$SIGN_DIR"/*.exe
"$SIGN_DIR"/bin/*.exe
"$SIGN_DIR"/bin/*.dll
"$SIGN_DIR"/bin/*.pyd
)
count=0
total=${#FILES[@]}
echo "Signing $total files"
for f in "${FILES[@]}"; do
((count+=1))
echo "Signing [$count/$total]: $f"
sign code artifact-signing \
--artifact-signing-endpoint "${WINDOWS_AZURE_ENDPOINT}" \
--artifact-signing-certificate-profile "${WINDOWS_AZURE_CERTIFICATE_PROFILE}" \
--artifact-signing-account "${WINDOWS_AZURE_SIGNING_ACCOUNT}" \
--timestamp-url https://timestamp.acs.microsoft.com \
--timestamp-digest sha256 \
"$f" >/dev/null 2>&1
# Output was redirected to /dev/null because Azure authentication is absurdly noisy, with constant misleading
# "failure" messages about Managed Identity authentication failing. We don't use, or want to use, that
# authentication, and the fact that it fails is not a problem as long as the real authentication succeeds.
done
# Manually check the important one!
signtool verify -pa "$SIGN_DIR/bin/FreeCAD.exe"
echo "Signing completed."
else
echo "Signing requested, but no Azure Artifact Signing available -- skipping signing."
fi
else
echo "Not logged into Azure -- skipping signing."
fi
7z a -t7z -mx9 -mmt=${NUMBER_OF_PROCESSORS} ${version_name}.7z ${version_name} -bb
# create hash
sha256sum ${version_name}.7z > ${version_name}.7z-SHA256.txt
@@ -88,6 +149,28 @@ if [ "${MAKE_INSTALLER}" == "true" ]; then
-X'SetCompressor /FINAL lzma' \
../../WindowsInstaller/FreeCAD-installer.nsi
mv ../../WindowsInstaller/${version_name}-installer.exe .
echo "Created installer ${version_name}-installer.exe"
# See if we can sign the installer exe as well:
if [[ "${WINDOWS_SIGN_RELEASE:-0}" == "1" ]] && \
az account get-access-token \
--tenant "$TENANT" \
--scope "https://codesigning.azure.net/.default" \
>/dev/null 2>&1;
then
echo "Signing the installer..."
sign code artifact-signing \
--artifact-signing-endpoint "${WINDOWS_AZURE_ENDPOINT}" \
--artifact-signing-certificate-profile "${WINDOWS_AZURE_CERTIFICATE_PROFILE}" \
--artifact-signing-account "${WINDOWS_AZURE_SIGNING_ACCOUNT}" \
--timestamp-url https://timestamp.acs.microsoft.com \
--timestamp-digest sha256 \
${version_name}-installer.exe >/dev/null 2>&1 \
|| { echo "Signing the installer failed!"; exit 1; }
else
echo "No code signing available, leaving the installer unsigned"
fi
sha256sum ${version_name}-installer.exe > ${version_name}-installer.exe-SHA256.txt
else
echo "Error: Failed to get NsProcess plugin. Aborting installer creation..."
@@ -96,8 +179,10 @@ if [ "${MAKE_INSTALLER}" == "true" ]; then
fi
if [ "${UPLOAD_RELEASE}" == "true" ]; then
echo "Uploading the release..."
gh release upload --clobber ${BUILD_TAG} "${version_name}.7z" "${version_name}.7z-SHA256.txt"
if [ "${MAKE_INSTALLER}" == "true" ]; then
gh release upload --clobber ${BUILD_TAG} "${version_name}-installer.exe" "${version_name}-installer.exe-SHA256.txt"
fi
echo "Done uploading"
fi
+2 -1
View File
@@ -19,11 +19,12 @@ debugpy = "*"
defusedxml = "*"
docutils = "*"
doxygen = "*"
eigen = "*"
eigen = ">=3.3,<5"
fmt = "*"
freetype = "*"
git = "*"
graphviz = "*"
harfbuzz = "*"
hdf5 = "*"
ifcopenshell = "*"
lark = "*"
+12 -1
View File
@@ -587,6 +587,15 @@ Document* Application::getDocument(const char *Name) const
return pos->second;
}
Document* Application::getDocumentOrActive(const char *Name) const
{
if (!Base::Tools::isNullOrEmpty(Name)) {
return getDocument(Name);
}
else {
return getActiveDocument();
}
}
const char * Application::getDocumentName(const Document* doc) const
{
@@ -2492,7 +2501,9 @@ void processProgramOptions(const boost::program_options::variables_map& vm, std:
void Application::initConfig(int argc, char ** argv)
{
// find the home path....
mConfig["AppHomePath"] = ApplicationDirectories::findHomePath(argv[0]).string();
mConfig["AppHomePath"] = Base::FileInfo::pathToString(
ApplicationDirectories::findHomePath(argv[0])
);
// Version of the application extracted from SubWCRef into src/Build/Version.h
// We only set these keys if not yet defined. Therefore it suffices to search
+52 -27
View File
@@ -35,10 +35,12 @@
#include <set>
#include <map>
#include <string>
#include <optional>
#include <Base/Observer.h>
#include <Base/Parameter.h>
#include <Base/ProgressIndicator.h>
#include "TransactionDefs.h"
// forward declarations
using PyObject = struct _object;
@@ -203,6 +205,8 @@ public:
*/
App::Document* getDocument(const char *Name) const;
App::Document* getDocumentOrActive(const char *Name) const;
/// %Path matching modes for getDocumentByPath()
enum class PathMatchMode
{
@@ -289,9 +293,11 @@ public:
/**
* @brief Setup a pending application-wide active transaction.
*
* Call this function to setup an application-wide transaction. All current
* pending transactions of opening documents will be committed first.
* However, no new transaction is created by this call. Any subsequent
* Call this function to setup a transaction in the currently active document
* if no document is active, a global transaction is created. If the current
* active document already has a transaction setup it will either commit the
* current transaction or rename it, depending on the tmpName flag of the
* currently setup transaction. No new transaction is created by this call. Any subsequent
* changes in any current opening document will auto create a transaction
* with the given name and ID. If more than one document is changed, the
* transactions will share the same ID, and will be undo/redo together.
@@ -303,32 +309,45 @@ public:
*
* @return The new transaction ID.
*/
int setActiveTransaction(const char* name, bool persist = false);
/**
* @brief Get the current active transaction name and ID.
int setActiveTransaction(TransactionName name);
/// Return the current global transaction name and ID if such a global transaction is
/// setup (uncommon)
std::string getActiveTransaction(int *tid=nullptr) const;
int openGlobalTransaction(TransactionName name);
int getGlobalTransaction() const;
bool transactionIsActive(int tid) const;
std::string getTransactionName(int tid) const;
bool transactionTmpName(int tid) const;
Document* transactionInitiator(int tid) const;
std::optional<TransactionDescription> transactionDescription(int tid) const;
void setTransactionDescription(int tid, const TransactionDescription& desc);
void setTransactionName(int tid, const TransactionName& name);
/** Commit/abort current active transactions
*
* If there is no active transaction, an empty string is returned.
*
* @param[out] tid If not `nullptr`, the current active transaction ID is
* returned through this pointer.
* @return The current active transaction name.
*/
const char* getActiveTransaction(int* tid = nullptr) const;
/**
* @brief Commit/abort current active transactions.
*
* @param[in] abort: whether to abort or commit the transactions
* @param[in] id: by default 0 meaning that the current active transaction ID is used.
*
* Besides calling this function directly, it will be called by
* automatically if 1) any new transaction is created with a different ID,
* or 2) any transaction with the current active transaction ID is either
* committed or aborted.
* @param[in] id: by default 0 meaning that the current global transaction ID is used.
*
* Bsides calling this function directly, it will be called by automatically
* if 1) any new transaction is created with a different ID, or 2) any
* transaction with the current active transaction ID is either committed or
* aborted
* returns true if it succeeded in closing the transaction
*/
void closeActiveTransaction(bool abort=false, int id=0);
/// @}
bool closeActiveTransaction(TransactionCloseMode mode = TransactionCloseMode::Commit, int id=0);
/// Internally call closeActiveTransaction(), but it makes the call site clearer
bool commitTransaction(int tid);
bool abortTransaction(int tid);
//@}
// NOLINTBEGIN
// clang-format off
@@ -1040,10 +1059,16 @@ private:
friend class AutoTransaction;
std::string _activeTransactionName;
int _activeTransactionID{0};
int _activeTransactionGuard{0};
bool _activeTransactionTmpName{false};
std::map<int, TransactionDescription> _activeTransactionDescriptions; // Maps transaction ID to transaction name
int currentlyClosingID {0};
// This is the transaction ID for a global transaction
// Documents will take this ID if it is non-zero
// and generate their own otherwise
int _globalTransactionID { 0 };
bool _globalTransactionTmpName {false};
std::string _globalTransactionName;
Base::ProgressIndicator _progressIndicator;
+9 -5
View File
@@ -80,12 +80,16 @@ const fs::path& ApplicationDirectories::getTempPath() const {
return this->_temp;
}
fs::path ApplicationDirectories::getTempFileName(const std::string & filename) const {
fs::path ApplicationDirectories::getTempFileName(const std::string & filename) const
{
auto tempPath = Base::FileInfo::pathToString(getTempPath());
if (filename.empty()) {
return Base::FileInfo::getTempFileName(nullptr, tempPath.c_str());
return Base::FileInfo::stringToPath(Base::FileInfo::getTempFileName(nullptr, tempPath.c_str())
);
}
return Base::FileInfo::getTempFileName(filename.c_str(), tempPath.c_str());
return Base::FileInfo::stringToPath(
Base::FileInfo::getTempFileName(filename.c_str(), tempPath.c_str())
);
}
const fs::path& ApplicationDirectories::getUserCachePath() const
@@ -182,7 +186,7 @@ void ApplicationDirectories::configurePaths(std::map<std::string,std::string>& m
bool keepDeprecatedPaths = mConfig.contains("KeepDeprecatedPaths");
// std paths
_home = fs::path(mConfig.at("AppHomePath"));
_home = Base::FileInfo::stringToPath(mConfig.at("AppHomePath"));
mConfig["BinPath"] = mConfig.at("AppHomePath") + "bin" + PATHSEP;
mConfig["DocPath"] = mConfig.at("AppHomePath") + "doc" + PATHSEP;
@@ -298,7 +302,7 @@ void ApplicationDirectories::configureResourceDirectory(const std::map<std::stri
_resource = Base::FileInfo::stringToPath(mConfig.at("AppHomePath")) / path;
}
#else
_resource = fs::path(mConfig.at("AppHomePath"));
_resource = Base::FileInfo::stringToPath(mConfig.at("AppHomePath"));
#endif
}
+9 -8
View File
@@ -242,9 +242,7 @@ PyMethodDef ApplicationPy::Methods[] = {
METH_VARARGS,
"setActiveTransaction(name, persist=False) -- setup active transaction with the given name\n\n"
"name: the transaction name\n"
"persist(False): by default, if the calling code is inside any invocation of a command, it\n"
" will be auto closed once all commands within the current stack exists. To\n"
" disable auto closing, set persist=True\n"
"persist(False): This parameter has no effect and is kept for compatibility reasonss"
"Returns the transaction ID for the active transaction. An application-wide\n"
"active transaction causes any document changes to open a transaction with\n"
"the given name and ID."},
@@ -1201,14 +1199,14 @@ PyObject* ApplicationPy::sGetDependentObjects(PyObject* /*self*/, PyObject* args
PyObject* ApplicationPy::sSetActiveTransaction(PyObject* /*self*/, PyObject* args)
{
char* name {};
PyObject* persist = Py_False;
PyObject* persist = Py_False; // Not used
if (!PyArg_ParseTuple(args, "s|O!", &name, &PyBool_Type, &persist)) {
return nullptr;
}
PY_TRY
{
Py::Long ret(GetApplication().setActiveTransaction(name, Base::asBoolean(persist)));
Py::Long ret(GetApplication().setActiveTransaction(TransactionName {.name=name, .temporary=false}));
return Py::new_reference_to(ret);
}
PY_CATCH;
@@ -1223,8 +1221,8 @@ PyObject* ApplicationPy::sGetActiveTransaction(PyObject* /*self*/, PyObject* arg
PY_TRY
{
int id = 0;
const char* name = GetApplication().getActiveTransaction(&id);
if (!name || id <= 0) {
std::string name = GetApplication().getActiveTransaction(&id);
if (name.empty() || id <= 0) {
Py_Return;
}
Py::Tuple ret(2);
@@ -1245,7 +1243,10 @@ PyObject* ApplicationPy::sCloseActiveTransaction(PyObject* /*self*/, PyObject* a
PY_TRY
{
GetApplication().closeActiveTransaction(Base::asBoolean(abort), id);
TransactionCloseMode mode = Base::asBoolean(abort)
? TransactionCloseMode::Abort
: TransactionCloseMode::Commit;
GetApplication().closeActiveTransaction(mode, id);
Py_Return;
}
PY_CATCH;
+167 -167
View File
@@ -35,204 +35,217 @@ FC_LOG_LEVEL_INIT("App", true, true)
using namespace App;
static int _TransactionLock;
static int _TransactionClosed;
AutoTransaction::AutoTransaction(const char* name, bool tmpName)
AutoTransaction::AutoTransaction(int tid)
: tid(tid)
{
auto& app = GetApplication();
if (name && app._activeTransactionGuard >= 0) {
if (!app.getActiveTransaction() || (!tmpName && app._activeTransactionTmpName)) {
FC_LOG("auto transaction '" << name << "', " << tmpName);
tid = app.setActiveTransaction(name);
app._activeTransactionTmpName = tmpName;
}
}
// We use negative transaction guard to disable auto transaction from here
// and any stack below. This is to support user setting active transaction
// before having any existing AutoTransaction on stack, or 'persist'
// transaction that can out live AutoTransaction.
if (app._activeTransactionGuard < 0) {
--app._activeTransactionGuard;
}
else if (tid || app._activeTransactionGuard > 0) {
++app._activeTransactionGuard;
}
else if (app.getActiveTransaction()) {
FC_LOG("auto transaction disabled because of '" << app._activeTransactionName << "'");
--app._activeTransactionGuard;
}
else {
++app._activeTransactionGuard;
}
FC_TRACE("construct auto Transaction " << app._activeTransactionGuard);
}
AutoTransaction::AutoTransaction(Document* doc, const std::string& name)
: AutoTransaction(doc->openTransaction(name))
{
}
AutoTransaction::~AutoTransaction()
{
auto& app = GetApplication();
FC_TRACE("before destruct auto Transaction " << app._activeTransactionGuard);
if (app._activeTransactionGuard < 0) {
++app._activeTransactionGuard;
}
else if (!app._activeTransactionGuard) {
#ifdef FC_DEBUG
FC_ERR("Transaction guard error");
#endif
}
else if (--app._activeTransactionGuard == 0) {
try {
// We don't call close() here, because close() only closes
// transaction that we opened during construction time. However,
// when _activeTransactionGuard reaches zero here, we are supposed
// to close any transaction opened.
app.closeActiveTransaction();
}
catch (Base::Exception& e) {
e.reportException();
}
catch (...) {
}
}
FC_TRACE("destruct auto Transaction " << app._activeTransactionGuard);
close(TransactionCloseMode::Commit);
}
void AutoTransaction::close(bool abort)
void AutoTransaction::close(TransactionCloseMode mode)
{
if (tid || abort) {
GetApplication().closeActiveTransaction(abort, abort ? 0 : tid);
if (tid != NullTransaction) {
GetApplication().closeActiveTransaction(mode, tid);
tid = 0;
}
}
void AutoTransaction::setEnable(bool enable)
int Application::setActiveTransaction(TransactionName name)
{
auto& app = GetApplication();
if (!app._activeTransactionGuard) {
return;
if (name.name.empty()) {
name.name = "Command";
}
if ((enable && app._activeTransactionGuard > 0)
|| (!enable && app._activeTransactionGuard < 0)) {
return;
}
app._activeTransactionGuard = -app._activeTransactionGuard;
FC_TRACE("toggle auto Transaction " << app._activeTransactionGuard);
if (!enable && app._activeTransactionTmpName) {
bool close = true;
for (auto& v : app.DocMap) {
if (v.second->hasPendingTransaction()) {
close = false;
break;
}
}
if (close) {
app.closeActiveTransaction();
}
if (_pActiveDoc != nullptr) {
return _pActiveDoc->setActiveTransaction(name);
}
return openGlobalTransaction(name);
}
int Application::setActiveTransaction(const char* name, bool persist)
std::string Application::getActiveTransaction(int* id) const
{
if (id != nullptr) {
*id = _globalTransactionID;
}
return _globalTransactionID != 0 ? getTransactionName(_globalTransactionID) : "";
}
int Application::openGlobalTransaction(TransactionName name)
{
if (name.name.empty()) {
name.name = "Command";
}
if (!name || !name[0]) {
name = "Command";
}
this->signalBeforeOpenTransaction(name);
if (_activeTransactionGuard > 0 && getActiveTransaction()) {
if (_activeTransactionTmpName) {
FC_LOG("transaction rename to '" << name << "'");
for (auto& v : DocMap) {
v.second->renameTransaction(name, _activeTransactionID);
FC_WARN("Setting a global transaction with name='" << name.name);
if (_globalTransactionID != 0 && transactionTmpName(_globalTransactionID)) {
setTransactionName(_globalTransactionID, name);
} else {
FC_LOG("set global transaction '" << name.name << "'");
if (_globalTransactionID != 0 && !commitTransaction(_globalTransactionID)) {
FC_WARN("could not close current global transaction");
return _globalTransactionID;
}
_globalTransactionID = Transaction::getNewID();
setTransactionDescription(
_globalTransactionID,
TransactionDescription {
.initiator = nullptr,
.name = name
}
}
else {
if (persist) {
AutoTransaction::setEnable(false);
}
return 0;
}
);
}
else if (_TransactionLock) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Transaction locked, ignore new transaction '" << name << "'");
}
return 0;
return _globalTransactionID;
}
int Application::getGlobalTransaction() const
{
return _globalTransactionID;
}
bool Application::transactionIsActive(int tid) const
{
return transactionDescription(tid) != std::nullopt;
}
std::string Application::getTransactionName(int tid) const
{
auto desc = transactionDescription(tid);
return desc ? desc->name.name : "";
}
bool Application::transactionTmpName(int tid) const
{
auto desc = transactionDescription(tid);
return desc ? desc->name.temporary : false;
}
Document* Application::transactionInitiator(int tid) const
{
auto desc = transactionDescription(tid);
return desc ? desc->initiator : nullptr;
}
std::optional<TransactionDescription> Application::transactionDescription(int tid) const
{
if (tid == NullTransaction) {
return std::nullopt;
}
else {
FC_LOG("set active transaction '" << name << "'");
_activeTransactionID = 0;
auto found = _activeTransactionDescriptions.find(tid);
if (found != _activeTransactionDescriptions.end()) {
return std::optional<TransactionDescription>(found->second);
}
return std::nullopt;
}
void Application::setTransactionDescription(int tid, const TransactionDescription& desc)
{
if (tid == NullTransaction) {
return;
}
auto found = _activeTransactionDescriptions.find(tid);
bool wasPresent = (found != _activeTransactionDescriptions.end());
if (wasPresent && found->second.name.temporary) {
for (auto& v : DocMap) {
v.second->_commitTransaction();
v.second->renameTransaction(desc.name.name, tid);
}
_activeTransactionID = Transaction::getNewID();
}
_activeTransactionTmpName = false;
_activeTransactionName = name;
if (persist) {
AutoTransaction::setEnable(false);
if (!wasPresent || found->second.name.temporary) {
_activeTransactionDescriptions[tid] = desc;
FC_LOG("transaction rename to '" << desc.name.name << "'");
}
return _activeTransactionID;
}
void Application::setTransactionName(int tid, const TransactionName& name)
{
if (tid == NullTransaction || !transactionIsActive(tid)) {
return;
}
auto found = _activeTransactionDescriptions.find(tid);
if (found == _activeTransactionDescriptions.end() || found->second.name.temporary) {
_activeTransactionDescriptions[tid].name = name;
FC_LOG("transaction rename to '" << name.name << "'");
for (auto& v : DocMap) {
v.second->renameTransaction(name.name, tid);
}
}
}
const char* Application::getActiveTransaction(int* id) const
bool Application::closeActiveTransaction(TransactionCloseMode mode, int id)
{
int tid = 0;
if (Transaction::getLastID() == _activeTransactionID) {
tid = _activeTransactionID;
}
if (id) {
*id = tid;
}
return tid ? _activeTransactionName.c_str() : nullptr;
}
void Application::closeActiveTransaction(bool abort, int id)
{
if (!id) {
id = _activeTransactionID;
}
if (!id) {
return;
}
if (_activeTransactionGuard > 0 && !abort) {
FC_LOG("ignore close transaction");
return;
}
if (_TransactionLock) {
if (_TransactionClosed >= 0) {
_TransactionLock = abort ? -1 : 1;
bool abort = (mode == TransactionCloseMode::Abort);
if (id == NullTransaction) {
if (_pActiveDoc != nullptr && _pActiveDoc->getBookedTransactionID() != NullTransaction) {
id = _pActiveDoc->getBookedTransactionID();
} else {
id = _globalTransactionID;
}
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
return;
}
if (id == NullTransaction || id == currentlyClosingID) {
return false;
}
currentlyClosingID = id;
FC_LOG("close transaction '" << _activeTransactionName << "' " << abort);
_activeTransactionID = 0;
TransactionSignaller signaller(abort, false);
for (auto& v : DocMap) {
if (v.second->getTransactionID(true) != id) {
std::vector<Document*> docsToPoke;
for (auto& docNameAndDoc : DocMap) {
if (docNameAndDoc.second->getBookedTransactionID() != id) {
continue;
}
if(docNameAndDoc.second->isTransactionLocked() || docNameAndDoc.second->transacting()) {
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
currentlyClosingID = 0;
return false;
}
if(docNameAndDoc.second->transacting()) {
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
currentlyClosingID = 0;
return false;
}
docsToPoke.push_back(docNameAndDoc.second);
}
FC_LOG("close transaction '" << _activeTransactionDescriptions[id].name.name << "' " << abort);
_activeTransactionDescriptions.erase(id);
if (id == _globalTransactionID) {
_globalTransactionID = 0;
}
TransactionSignaller signaller(abort, false);
for (auto& doc : docsToPoke) {
if (abort) {
v.second->_abortTransaction();
doc->_abortTransaction();
}
else {
v.second->_commitTransaction();
doc->_commitTransaction();
}
}
currentlyClosingID = 0;
return true;
}
bool Application::commitTransaction(int tid)
{
return closeActiveTransaction(TransactionCloseMode::Commit, tid);
}
bool Application::abortTransaction(int tid)
{
return closeActiveTransaction(TransactionCloseMode::Abort, tid);
}
////////////////////////////////////////////////////////////////////////
TransactionLocker::TransactionLocker(bool lock)
TransactionLocker::TransactionLocker(Document* doc, bool lock)
: active(lock)
, doc(doc)
{
if (lock) {
++_TransactionLock;
doc->lockTransaction();
}
}
@@ -267,22 +280,9 @@ void TransactionLocker::activate(bool enable)
active = enable;
if (active) {
++_TransactionLock;
doc->lockTransaction();
return;
}
if (--_TransactionLock != 0) {
return;
}
if (_TransactionClosed) {
bool abort = (_TransactionClosed < 0);
_TransactionClosed = 0;
GetApplication().closeActiveTransaction(abort);
}
}
bool TransactionLocker::isLocked()
{
return _TransactionLock > 0;
doc->unlockTransaction();
}
+22 -48
View File
@@ -26,11 +26,15 @@
#include <cstddef>
#include <FCGlobal.h>
#include <string>
#include "TransactionDefs.h"
namespace App
{
class Application;
class Document;
/**
* @brief A helper class to manage transactions (i.e. undo/redo).
@@ -45,59 +49,31 @@ public:
void* operator new(std::size_t) = delete;
public:
/**
* @brief Construct an auto transaction.
*
* @param[in] name: optional new transaction name on construction
* @param[in] tmpName: if true and a new transaction is setup, the name given is
* considered as temporary, and subsequent construction of this class (or
* calling Application::setActiveTransaction()) can override the transaction
* name.
*
* The constructor increments an internal counter
* (Application::_activeTransactionGuard). The counter prevents any new
* active transactions being setup. It also prevents to close
* (i.e. commits) the current active transaction until it reaches zero. It
* does not have any effect on aborting transactions though.
/** Constructor
*
* @param tid the ID of the transaction to manage
*
* No action is done in the constructor
*/
AutoTransaction(const char* name = nullptr, bool tmpName = false);
/**
* @brief Destruct an auto transaction.
*
* This destructor decrease an internal counter
* (Application::_activeTransactionGuard), and will commit any current
* active transaction when the counter reaches zero.
explicit AutoTransaction(int tid);
AutoTransaction(Document* doc, const std::string& name);
/** Destructor
*
* This destructor attempts to commit the transaction it manages
*/
~AutoTransaction();
/**
* @brief Close or abort the transaction.
*
* This function can be used to explicitly close (i.e. commit) the
* transaction, if the current transaction ID matches the one created inside
* the constructor. For aborting, it will abort any current transaction.
*
* @param[in] abort: if true, abort the transaction; otherwise, commit it.
* This function can be used to explicitly close (i.e. commit / abort) the
* transaction,
*/
void close(bool abort = false);
/**
* @brief Enable/Disable any AutoTransaction instance on the current stack.
*
* Once disabled, any empty temporary named transaction is closed. If there
* are non-empty or non-temporary named active transaction, it will not be
* auto closed.
*
* This function may be used in, for example, Gui::Document::setEdit() to
* allow a transaction live past any command scope.
*
* @param[in] enable: if true, enable the AutoTransaction; otherwise, disable it.
*/
static void setEnable(bool enable);
void close(TransactionCloseMode mode = TransactionCloseMode::Commit);
private:
int tid = 0;
int tid { 0 };
};
@@ -116,7 +92,7 @@ public:
*
* @param[in] lock: whether to activate the lock
*/
TransactionLocker(bool lock = true);
TransactionLocker(Document* doc, bool lock = true);
/**
* @brief Destruct a transaction locker.
@@ -142,10 +118,7 @@ public:
{
return active;
}
/// Check if transaction is being locked.
static bool isLocked();
friend class Application;
public:
@@ -154,6 +127,7 @@ public:
private:
bool active;
Document* doc;
};
} // namespace App
+199 -101
View File
@@ -35,6 +35,7 @@
#include <list>
#include <algorithm>
#include <filesystem>
#include <format>
#include <boost/algorithm/string.hpp>
#include <boost/bimap.hpp>
@@ -190,6 +191,7 @@ bool Document::undo(const int id)
mRedoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
mRedoTransactions.push_back(d->activeUndoTransaction);
d->activeUndoTransaction = nullptr;
d->bookedTransaction = 0;
mUndoMap.erase(mUndoTransactions.back()->getID());
delete mUndoTransactions.back();
@@ -242,6 +244,7 @@ bool Document::redo(const int id)
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
mUndoTransactions.push_back(d->activeUndoTransaction);
d->activeUndoTransaction = nullptr;
d->bookedTransaction = 0;
mRedoMap.erase(mRedoTransactions.back()->getID());
delete mRedoTransactions.back();
@@ -271,10 +274,10 @@ void Document::changePropertyOfObject(TransactionalObject* obj,
}
if ((d->iUndoMode != 0) && !isPerformingTransaction() && !d->activeUndoTransaction) {
if (!testStatus(Restoring) || testStatus(Importing)) {
int tid = 0;
const char* name = GetApplication().getActiveTransaction(&tid);
if (name && tid > 0) {
_openTransaction(name, tid);
if (d->bookedTransaction == NullTransaction) {
d->bookedTransaction = GetApplication().getGlobalTransaction();
} else {
_openTransaction(GetApplication().getTransactionName(d->bookedTransaction), d->bookedTransaction);
}
}
}
@@ -329,71 +332,92 @@ std::vector<std::string> Document::getAvailableRedoNames() const
return vList;
}
void Document::openTransaction(const char* name) // NOLINT
int Document::openTransaction(TransactionName name, int tid) // NOLINT
{
if (isPerformingTransaction() || d->committing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot open transaction while transacting");
}
return;
if (tid != NullTransaction && tid == d->bookedTransaction) {
return tid; // Early exit without warning
}
if (isTransactionLocked()) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Transaction locked, ignore new transaction '" << name.name << "'");
}
return 0;
}
GetApplication().setActiveTransaction(name ? name : "<empty>");
}
int Document::_openTransaction(const char* name, int id)
{
if (isPerformingTransaction() || d->committing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot open transaction while transacting");
}
return 0;
}
if (d->iUndoMode != 0) {
// Avoid recursive calls that is possible while
// clearing the redo transactions and will cause
// a double deletion of some transaction and thus
// a segmentation fault
if (d->opentransaction) {
return 0;
}
Base::FlagToggler<> flag(d->opentransaction);
if ((id != 0) && mUndoMap.find(id) != mUndoMap.end()) {
throw Base::RuntimeError("invalid transaction id");
}
if (d->activeUndoTransaction) {
_commitTransaction(true);
}
_clearRedos();
d->activeUndoTransaction = new Transaction(id);
if (!name) {
name = "<empty>";
}
d->activeUndoTransaction->Name = name;
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
id = d->activeUndoTransaction->getID();
signalOpenTransaction(*this, name);
auto& app = GetApplication();
auto activeDoc = app.getActiveDocument();
if (activeDoc && activeDoc != this && !activeDoc->hasPendingTransaction()) {
std::string aname("-> ");
aname += d->activeUndoTransaction->Name;
FC_LOG("auto transaction " << getName() << " -> " << activeDoc->getName());
activeDoc->_openTransaction(aname.c_str(), id);
}
return id;
if (name.name.empty()) {
name.name = "<empty>";
}
return 0;
return setActiveTransaction(name, tid);
}
int Document::openTransaction(std::string name, int tid)
{
return openTransaction(TransactionName {.name = name, .temporary = false}, tid);
}
void Document::renameTransaction(const char* name, const int id) const
int Document::_openTransaction(std::string name, int id)
{
if (name && d->activeUndoTransaction && d->activeUndoTransaction->getID() == id) {
if (isTransactionLocked() && id != d->bookedTransaction) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Transaction locked, ignore new transaction '" << name << "'");
}
}
if (isPerformingTransaction() || d->committing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot open transaction while transacting");
}
return 0;
}
if (d->iUndoMode == 0) {
return 0;
}
// Avoid recursive calls that is possible while
// clearing the redo transactions and will cause
// a double deletion of some transaction and thus
// a segmentation fault
if (d->opentransaction) {
return 0;
}
Base::FlagToggler<> flag(d->opentransaction);
if ((id != 0) && mUndoMap.find(id) != mUndoMap.end()) {
throw Base::RuntimeError("invalid transaction id");
}
if (d->activeUndoTransaction) {
_commitTransaction(true);
}
_clearRedos();
// When id == 0, this creates a new id
// for instance, when there is no global transaction
// from the application to stick to
d->activeUndoTransaction = new Transaction(id);
if (name.empty()) {
name = "<empty>";
}
d->activeUndoTransaction->Name = name;
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
id = d->activeUndoTransaction->getID();
signalOpenTransaction(*this, name);
Document* transactionInitiator = GetApplication().transactionInitiator(id);
if (transactionInitiator && transactionInitiator != this && !transactionInitiator->hasPendingTransaction()) {
std::string aname = std::format("-> {}", d->activeUndoTransaction->Name);
FC_LOG("auto transaction " << getName() << " -> " << transactionInitiator->getName());
transactionInitiator->_openTransaction(aname, id);
}
return id;
}
void Document::renameTransaction(const std::string& name, const int id) const
{
if (!name.empty() && d->activeUndoTransaction && d->activeUndoTransaction->getID() == id) {
if (boost::starts_with(d->activeUndoTransaction->Name, "-> ")) {
d->activeUndoTransaction->Name.resize(3);
}
@@ -403,48 +427,108 @@ void Document::renameTransaction(const char* name, const int id) const
d->activeUndoTransaction->Name += name;
}
}
int Document::setActiveTransaction(TransactionName name, int tid)
{
// Probably a group transaction situation
if (tid != NullTransaction) {
if (!GetApplication().transactionIsActive(tid)) {
FC_LOG("Could not set active transaction to inactive ID");
return NullTransaction;
}
if (d->bookedTransaction != NullTransaction && d->bookedTransaction != tid && !_commitTransaction(true)) {
FC_LOG("Could not book transaction for document");
return NullTransaction;
}
d->bookedTransaction = tid;
if (GetApplication().transactionTmpName(d->bookedTransaction)) {
GetApplication().setTransactionName(d->bookedTransaction, name);
}
return d->bookedTransaction;
}
// Rename the transaction if it had a tmp name
if (d->bookedTransaction != NullTransaction && GetApplication().transactionTmpName(d->bookedTransaction)) {
GetApplication().setTransactionName(d->bookedTransaction, name);
return d->bookedTransaction;
}
if (d->bookedTransaction != NullTransaction && !_commitTransaction(true)) {
FC_LOG("Could not book transaction for document");
return NullTransaction;
}
d->bookedTransaction = Transaction::getNewID();
GetApplication().setTransactionDescription(d->bookedTransaction, TransactionDescription {.initiator = this, .name = name});
return d->bookedTransaction;
}
void Document::lockTransaction()
{
d->TransactionLock++;
}
void Document::unlockTransaction()
{
if (d->TransactionLock > 0) {
d->TransactionLock--;
}
}
bool Document::isTransactionLocked() const
{
return d->TransactionLock > 0;
}
bool Document::transacting() const
{
return isPerformingTransaction() || d->committing;
}
void Document::_checkTransaction(DocumentObject* pcDelObj, const Property* What, int line)
{
// if the undo is active but no transaction open, open one!
if ((d->iUndoMode != 0) && !isPerformingTransaction()) {
if (!d->activeUndoTransaction) {
if (!testStatus(Restoring) || testStatus(Importing)) {
int tid = 0;
const char* name = GetApplication().getActiveTransaction(&tid);
if (name && tid > 0) {
bool ignore = false;
if (What && What->testStatus(Property::NoModify)) {
ignore = true;
}
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
if (What) {
FC_LOG((ignore ? "ignore" : "auto")
<< " transaction (" << line << ") '" << What->getFullName());
}
else {
FC_LOG((ignore ? "ignore" : "auto") << " transaction (" << line << ") '"
<< name << "' in " << getName());
}
}
if (!ignore) {
_openTransaction(name, tid);
}
return;
if (d->iUndoMode == 0 || isPerformingTransaction() || d->activeUndoTransaction) {
return;
}
if (!testStatus(Restoring) || testStatus(Importing)) {
// Priority to a transaction that has been booked
// explicitly for this document, it there are none
// get a sticky transaction from application
if (!d->bookedTransaction) {
d->bookedTransaction = GetApplication().getGlobalTransaction();
}
if (d->bookedTransaction != NullTransaction) {
std::string name = GetApplication().getTransactionName(d->bookedTransaction);
bool ignore = false;
if (What && What->testStatus(Property::NoModify)) {
ignore = true;
}
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
if (What) {
FC_LOG((ignore ? "ignore" : "auto")
<< " transaction (" << line << ") '" << What->getFullName());
}
else {
FC_LOG((ignore ? "ignore" : "auto") << " transaction (" << line << ") '"
<< name << "' in " << getName());
}
}
if (!pcDelObj) {
return;
}
// When the object is going to be deleted we have to check if it has already been added
// to the undo transactions
std::list<Transaction*>::iterator it;
for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) {
if ((*it)->hasObject(pcDelObj)) {
_openTransaction("Delete");
break;
}
if (!ignore) {
_openTransaction(name, d->bookedTransaction);
}
return;
}
}
if (!pcDelObj) {
return;
}
// When the object is going to be deleted we have to check if it has already been added
// to the undo transactions
std::list<Transaction*>::iterator it;
for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) {
if ((*it)->hasObject(pcDelObj)) {
_openTransaction("Delete");
break;
}
}
}
@@ -473,29 +557,36 @@ void Document::commitTransaction() // NOLINT
}
if (d->activeUndoTransaction) {
GetApplication().closeActiveTransaction(false, d->activeUndoTransaction->getID());
// This will iterate over all documents and ask them to
// commit their transaction if their ID matches
GetApplication().commitTransaction(d->activeUndoTransaction->getID());
} else {
d->bookedTransaction = 0; // Reset booked transaction even if it was not used
}
}
void Document::_commitTransaction(const bool notify)
bool Document::_commitTransaction(const bool notify)
{
if (isPerformingTransaction()) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot commit transaction while transacting");
}
return;
return false;
}
if (d->committing) {
// for a recursive call return without printing a warning
return;
return false;
}
d->bookedTransaction = 0;
if (d->activeUndoTransaction) {
Base::FlagToggler<> flag(d->committing);
Application::TransactionSignaller signaller(false, true);
const int id = d->activeUndoTransaction->getID();
mUndoTransactions.push_back(d->activeUndoTransaction);
d->activeUndoTransaction = nullptr;
// check the stack for the limits
if (mUndoTransactions.size() > d->UndoMaxStackSize) {
mUndoMap.erase(mUndoTransactions.front()->getID());
@@ -504,11 +595,12 @@ void Document::_commitTransaction(const bool notify)
}
signalCommitTransaction(*this);
// closeActiveTransaction() may call again _commitTransaction()
// commitTransaction() may call again _commitTransaction()
if (notify) {
GetApplication().closeActiveTransaction(false, id);
GetApplication().commitTransaction(id);
}
}
return true;
}
void Document::abortTransaction() const
@@ -520,7 +612,9 @@ void Document::abortTransaction() const
return;
}
if (d->activeUndoTransaction) {
GetApplication().closeActiveTransaction(true, d->activeUndoTransaction->getID());
GetApplication().abortTransaction(d->activeUndoTransaction->getID());
} else {
d->bookedTransaction = 0; // Reset booked transaction even if it was not used
}
}
@@ -532,6 +626,7 @@ void Document::_abortTransaction()
}
}
d->bookedTransaction = 0;
if (d->activeUndoTransaction) {
Base::FlagToggler<bool> flag(d->rollback);
Application::TransactionSignaller signaller(true, true);
@@ -575,7 +670,10 @@ int Document::getTransactionID(const bool undo, unsigned pos) const
for (; pos != 0U; ++rit, --pos) {}
return (*rit)->getID();
}
int Document::getBookedTransactionID() const
{
return d->bookedTransaction;
}
bool Document::isTransactionEmpty() const
{
return !d->activeUndoTransaction;
@@ -3312,7 +3410,7 @@ void Document::_removeObject(DocumentObject* pcObject, RemoveObjectOptions optio
return;
}
TransactionLocker tlock;
TransactionLocker tlock(this);
_checkTransaction(pcObject, nullptr, __LINE__);
+18 -7
View File
@@ -35,6 +35,7 @@
#include "PropertyLinks.h"
#include "PropertyStandard.h"
#include "ExportInfo.h"
#include "TransactionDefs.h"
#include <map>
#include <vector>
@@ -865,7 +866,19 @@ public:
* setup a potential transaction that will only be created if there are
* actual changes.
*/
void openTransaction(const char* name = nullptr);
int openTransaction(TransactionName name, int tid = 0);
int openTransaction(std::string name, int tid = 0);
// If the tid != 0, it will take the transaction id if it exists
int setActiveTransaction(TransactionName name, int tid = 0);
void lockTransaction();
void unlockTransaction();
bool isTransactionLocked() const;
bool transacting() const;
int getBookedTransactionID() const;
/**
* @brief Rename the current transaction.
@@ -875,7 +888,7 @@ public:
* @param[in] name The new name of the transaction.
* @param[in] id The transaction ID to match.
*/
void renameTransaction(const char* name, int id) const;
void renameTransaction(const std::string& name, int id) const;
/**
* @brief Commit the Command transaction.
@@ -1244,7 +1257,7 @@ public:
/// Check if there is any document restoring/importing.
static bool isAnyRestoring();
/// Register a new label.
void registerLabel(const std ::string& newLabel);
/// Unregister a label.
@@ -1409,8 +1422,7 @@ protected:
* This function creates an actual transaction regardless of Application
* AutoTransaction setting.
*/
int _openTransaction(const char* name = nullptr, int id = 0);
int _openTransaction(std::string name = "", int id = 0);
/**
* @brief Commit the Command transaction.
*
@@ -1419,8 +1431,7 @@ protected:
*
* @param notify If true, notify the application to close the transaction.
*/
void _commitTransaction(bool notify = false);
bool _commitTransaction(bool notify = false);
/**
* @brief Abort the running transaction.
*
+9
View File
@@ -447,3 +447,12 @@ class Document(PropertyContainer):
sort: whether to topologically sort the return list
"""
...
def getBookedTransactionID(self) -> int:
"""
getBookedTransactionID() -> int
Returns the currently booked transaction id, which is the id of the current transaction OR the id
the next transaction will stick to if no change has occured yet
"""
...
+9
View File
@@ -1175,6 +1175,15 @@ PyObject* DocumentPy::getDependentDocuments(PyObject* args)
}
PY_CATCH;
}
PyObject* DocumentPy::getBookedTransactionID(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
int tid = getDocumentPtr()->getBookedTransactionID();
return Py::new_reference_to(Py::Long(tid));
}
Py::Boolean DocumentPy::getRestoring() const
{
+2 -1
View File
@@ -328,7 +328,7 @@ Base::Placement GeoFeature::getGlobalPlacement(App::DocumentObject* targetObj,
Base::Placement GeoFeature::getGlobalPlacement(App::DocumentObject* targetObj,
App::PropertyXLinkSub* prop)
{
if (!targetObj || !prop) {
if (!prop) {
return Base::Placement();
}
@@ -357,3 +357,4 @@ Base::Placement GeoFeature::getGlobalPlacement(const DocumentObject* obj)
return placementProperty->getValue();
}
+3 -3
View File
@@ -1546,11 +1546,11 @@ void PropertyString::setValue(const char* newValue)
// OnProposedLabelChange has changed the new value to what the current value is
return;
}
if (!propChanges.empty() && !GetApplication().getActiveTransaction()) {
if (!propChanges.empty() && obj->getDocument()->getBookedTransactionID() == 0) {
commit = true;
std::ostringstream str;
str << "Change " << obj->getNameInDocument() << ".Label";
GetApplication().setActiveTransaction(str.str().c_str());
obj->getDocument()->openTransaction(str.str().c_str());
}
}
@@ -1563,7 +1563,7 @@ void PropertyString::setValue(const char* newValue)
}
if (commit) {
GetApplication().closeActiveTransaction();
obj->getDocument()->commitTransaction();
}
}
+1 -1
View File
@@ -75,7 +75,7 @@ Man bør derefter klikke på Opdater knappen i hovedværktøjslinjen.
<message>
<location filename="../../Datums.cpp" line="271"/>
<source>Origin</source>
<translation>Origo</translation>
<translation>Nulpunkt</translation>
</message>
</context>
</TS>
+11 -11
View File
@@ -4,7 +4,7 @@
<context>
<name>LinkParams</name>
<message>
<location filename="../../Link.cpp" line="135"/>
<location filename="../../Link.cpp" line="131"/>
<source>Stores the last user choice of whether to apply CopyOnChange setup to all links
that reference the same configurable object</source>
<translation>Menyimpan pilihan terakhir pengguna sama ada untuk menerapkan tetapan CopyOnChange kepada semua pautan yang merujuk kepada objek boleh dikonfigurasikan yang sama</translation>
@@ -13,7 +13,7 @@ that reference the same configurable object</source>
<context>
<name>QObject</name>
<message>
<location filename="../../Application.cpp" line="457"/>
<location filename="../../Application.cpp" line="458"/>
<source>Unnamed</source>
<translation>Tidak bernama</translation>
</message>
@@ -21,7 +21,7 @@ that reference the same configurable object</source>
<context>
<name>App::OriginGroupExtension</name>
<message>
<location filename="../../OriginGroupExtension.cpp" line="162"/>
<location filename="../../OriginGroupExtension.cpp" line="161"/>
<source>Origin</source>
<translation>Asal</translation>
</message>
@@ -29,7 +29,7 @@ that reference the same configurable object</source>
<context>
<name>Notifications</name>
<message>
<location filename="../../Document.cpp" line="1095"/>
<location filename="../../Document.cpp" line="1117"/>
<source>
It is recommended that the user right-click the root of the document and select Mark to recompute.
The user should then click the Refresh button in the main toolbar.
@@ -41,37 +41,37 @@ Pengguna kemudiannya perlu mengklik butang Muat semula dalam bar alat utama.</tr
<context>
<name>App::LocalCoordinateSystem</name>
<message>
<location filename="../../Datums.cpp" line="250"/>
<location filename="../../Datums.cpp" line="265"/>
<source>X-axis</source>
<translation>Paksi-X</translation>
</message>
<message>
<location filename="../../Datums.cpp" line="251"/>
<location filename="../../Datums.cpp" line="266"/>
<source>Y-axis</source>
<translation>Paksi-Y</translation>
</message>
<message>
<location filename="../../Datums.cpp" line="252"/>
<location filename="../../Datums.cpp" line="267"/>
<source>Z-axis</source>
<translation>Paksi-Z</translation>
</message>
<message>
<location filename="../../Datums.cpp" line="253"/>
<location filename="../../Datums.cpp" line="268"/>
<source>XY-plane</source>
<translation>Pesawat-XY</translation>
</message>
<message>
<location filename="../../Datums.cpp" line="254"/>
<location filename="../../Datums.cpp" line="269"/>
<source>XZ-plane</source>
<translation>Pesawat-XZ</translation>
</message>
<message>
<location filename="../../Datums.cpp" line="255"/>
<location filename="../../Datums.cpp" line="270"/>
<source>YZ-plane</source>
<translation>Pesawat-YZ</translation>
</message>
<message>
<location filename="../../Datums.cpp" line="256"/>
<location filename="../../Datums.cpp" line="271"/>
<source>Origin</source>
<translation>Asal</translation>
</message>
+3 -3
View File
@@ -58,10 +58,10 @@ static void _replaceDirs()
};
for (auto const d : dirs) {
auto const path = temp_base + PATHSEP + d + PATHSEP;
auto const qpath = QString::fromStdString(path);
auto const path = temp_base + "/" + d + "/";
auto const qpath = QDir::cleanPath(QString::fromStdString(path));
QDir().mkpath(qpath);
config[d] = path;
config[d] = QDir::toNativeSeparators(qpath).toStdString();
}
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2026 Théo Veilleux-Trinh <theo.veilleux.trinh@proton.me>*
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_TRANSACTIONDEFS_H_
#define APP_TRANSACTIONDEFS_H_
#include <string>
namespace App {
class Document;
constexpr int NullTransaction = 0;
struct TransactionName {
// Name of the transaction as it will appear in the GUI
std::string name;
// If true, the transaction is allowed to be renamed
bool temporary { false };
};
struct TransactionDescription {
// Document on which the transaction was first created
// useful for mass transaction (e.g. delete from an assembly)
// where other documents may use the same transaction id
Document* initiator { nullptr };
TransactionName name;
};
enum class TransactionCloseMode {
Commit = 0,
Abort = 1,
};
}
#endif
+1 -1
View File
@@ -556,4 +556,4 @@ TransactionObject* TransactionFactory::createTransaction(const Base::Type& type)
Base::Console().log("Cannot create transaction object from %s\n", type.getName());
return nullptr;
}
}
+3 -1
View File
@@ -30,6 +30,7 @@
#include <Base/Factory.h>
#include <Base/Persistence.h>
#include <App/PropertyContainer.h>
#include "TransactionDefs.h"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
@@ -61,6 +62,7 @@ class AppExport Transaction: public Base::Persistence
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
/**
* @brief Construct a transaction.
@@ -71,7 +73,7 @@ public:
* transactions from different document, so that they can be undone/redone
* together.
*/
explicit Transaction(int id = 0);
explicit Transaction(int id = NullTransaction);
~Transaction() override;
+6
View File
@@ -34,6 +34,7 @@
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <optional>
#include <boost/bimap.hpp>
#include <boost/graph/adjacency_list.hpp>
@@ -93,6 +94,11 @@ struct DocumentP
int iUndoMode {0};
unsigned int UndoMemSize {0};
unsigned int UndoMaxStackSize {20};
unsigned int TransactionLock {0};
// Id and name that the next transaction will take
// as soon as there is a change to the document
int bookedTransaction { 0 };
std::string programVersion;
mutable HasherMap hashers;
std::multimap<const App::DocumentObject*, std::unique_ptr<App::DocumentObjectExecReturn>>
+23 -23
View File
@@ -4,54 +4,54 @@
<context>
<name>UnitsApi</name>
<message>
<location filename="../../UnitsSchemasData.h" line="85"/>
<location filename="../../UnitsSchemasData.h" line="90"/>
<source>Standard (mm, kg, s, °)</source>
<translation type="unfinished">Standard (mm, kg, s, °)</translation>
<translation>Standard (mm, kg, s, °)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="315"/>
<location filename="../../UnitsSchemasData.h" line="326"/>
<source>MKS (m, kg, s, °)</source>
<translation type="unfinished">MKS (m, kg, s, °)</translation>
<translation>MKS (m, kg, s, °)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="579"/>
<location filename="../../UnitsSchemasData.h" line="593"/>
<source>US customary (in, lb)</source>
<translation type="unfinished">US customary (in, lb)</translation>
<translation>Adat AS (in, lb)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="70"/>
<location filename="../../UnitsSchemasData.h" line="642"/>
<source>Imperial for Civil Eng (ft, lb, mph)</source>
<translation>Imperial Civil Eng (ft, lb, mph)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="75"/>
<source>Imperial decimal (in, lb)</source>
<translation type="unfinished">Imperial decimal (in, lb)</translation>
<translation>Imperial decimal (in, lb)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="540"/>
<location filename="../../UnitsSchemasData.h" line="554"/>
<source>Building Euro (cm, m², m³)</source>
<translation type="unfinished">Building Euro (cm, m², m³)</translation>
<translation>Bagunan Euro (cm, m², m³)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="617"/>
<location filename="../../UnitsSchemasData.h" line="631"/>
<source>Building US (ft-in, sqft, cft)</source>
<translation type="unfinished">Building US (ft-in, sqft, cft)</translation>
<translation>Membina AS (ft-in, sqft, cft)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="48"/>
<location filename="../../UnitsSchemasData.h" line="53"/>
<source>Metric small parts &amp; CNC (mm, mm/min)</source>
<translation type="unfinished">Metric small parts &amp; CNC (mm, mm/min)</translation>
<translation>Bahagian kecil metrik &amp; CNC (mm, mm/min)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="628"/>
<source>Imperial for Civil Eng (ft, ft/s)</source>
<translation type="unfinished">Imperial for Civil Eng (ft, ft/s)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="567"/>
<location filename="../../UnitsSchemasData.h" line="581"/>
<source>FEM (mm, N, s)</source>
<translation type="unfinished">FEM (mm, N, s)</translation>
<translation>FEM (mm, N, s)</translation>
</message>
<message>
<location filename="../../UnitsSchemasData.h" line="57"/>
<location filename="../../UnitsSchemasData.h" line="62"/>
<source>Meter decimal (m, m², m³)</source>
<translation type="unfinished">Meter decimal (m, m², m³)</translation>
<translation>Perpuluhan meter (m, m², m³)</translation>
</message>
</context>
</TS>
+3 -3
View File
@@ -157,7 +157,7 @@ long NavlibInterface::SetActiveCommand(std::string commandId)
if (!std::string(command->getName()).compare(parsedData.commandName)) {
if (parsedData.actionIndex == -1) {
Gui::Action* pAction = command->getAction();
if (pAction != nullptr) {
if (pAction) {
pAction->action()->trigger();
}
}
@@ -177,7 +177,7 @@ void NavlibInterface::unpackCommands(Gui::Command& command,
TDxCategory& category,
std::vector<TDx::CImage>& images)
{
if (command.getAction() == nullptr)
if (!command.getAction())
return;
QList<QAction*> pQActions;
@@ -185,7 +185,7 @@ void NavlibInterface::unpackCommands(Gui::Command& command,
int32_t index = -1;
auto actionGroup = qobject_cast<Gui::ActionGroup*>(command.getAction());
if (actionGroup != nullptr) {
if (actionGroup) {
pQActions = actionGroup->actions();
std::string subCategoryName = actionGroup->text().toStdString();
subCategory = TDxCategory(subCategoryName, subCategoryName);
+17 -17
View File
@@ -58,7 +58,7 @@ NavlibInterface::~NavlibInterface()
{
disableNavigation();
if (pivot.pVisibility != nullptr)
if (pivot.pVisibility)
pivot.pVisibility->unref();
}
@@ -97,7 +97,7 @@ long NavlibInterface::GetPointerPosition(navlib::point_t& position) const
if (is3DView()) {
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
if (inventorViewer == nullptr)
if (!inventorViewer)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
QPoint viewPoint = currentView.pView3d->mapFromGlobal(QCursor::pos());
@@ -122,7 +122,7 @@ CameraType NavlibInterface::getCamera() const
{
if (is3DView()) {
const Gui::View3DInventorViewer* inventorViewer = currentView.pView3d->getViewer();
if (inventorViewer != nullptr)
if (inventorViewer)
return dynamic_cast<CameraType>(inventorViewer->getCamera());
}
return nullptr;
@@ -132,18 +132,18 @@ template SoCamera* NavlibInterface::getCamera<SoCamera*>() const;
void NavlibInterface::onViewChanged(const Gui::MDIView* view)
{
if (view == nullptr)
if (!view)
return;
currentView.pView3d = dynamic_cast<const Gui::View3DInventor*>(view);
currentView.pView2d = nullptr;
if (currentView.pView3d != nullptr) {
if (currentView.pView3d) {
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
if (inventorViewer == nullptr)
if (!inventorViewer)
return;
auto pGroup = dynamic_cast<SoGroup* const>(inventorViewer->getSceneGraph());
if (pGroup == nullptr)
if (!pGroup)
return;
if (pGroup->findChild(pivot.pVisibility) == -1)
@@ -198,11 +198,11 @@ void NavlibInterface::enableNavigation()
void NavlibInterface::connectActiveTab()
{
auto pQMdiArea = Gui::MainWindow::getInstance()->findChild<QMdiArea*>();
if (pQMdiArea == nullptr)
if (!pQMdiArea)
return;
auto pQTabBar = pQMdiArea->findChild<QTabBar*>();
if (pQTabBar == nullptr)
if (!pQTabBar)
return;
pQTabBar->connect(pQTabBar, &QTabBar::currentChanged, [this, pQTabBar](int idx) {
@@ -222,7 +222,7 @@ long NavlibInterface::GetCameraMatrix(navlib::matrix_t& matrix) const
if (is3DView()) {
auto pCamera = getCamera<SoCamera*>();
if (pCamera == nullptr)
if (!pCamera)
return navlib::make_result_code(navlib::navlib_errc::function_not_supported);
SbMatrix cameraMatrix;
@@ -268,7 +268,7 @@ long NavlibInterface::SetCameraMatrix(const navlib::matrix_t& matrix)
if (is3DView()) {
auto pCamera = getCamera<SoCamera*>();
if (pCamera == nullptr)
if (!pCamera)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
SbMatrix cameraMatrix(matrix(0, 0), matrix(0, 1), matrix(0, 2), matrix(0, 3),
@@ -288,7 +288,7 @@ long NavlibInterface::SetCameraMatrix(const navlib::matrix_t& matrix)
long NavlibInterface::GetViewFrustum(navlib::frustum_t& frustum) const
{
const auto pCamera = getCamera<SoPerspectiveCamera* const>();
if (pCamera == nullptr)
if (!pCamera)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
const SbViewVolume viewVolume = pCamera->getViewVolume(pCamera->aspectRatio.getValue());
@@ -327,7 +327,7 @@ long NavlibInterface::GetViewExtents(navlib::box_t& extents) const
}
const auto pCamera = getCamera<SoOrthographicCamera* const>();
if (pCamera == nullptr)
if (!pCamera)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
const SbViewVolume viewVolume = pCamera->getViewVolume(pCamera->aspectRatio.getValue());
@@ -371,7 +371,7 @@ long NavlibInterface::SetViewExtents(const navlib::box_t& extents)
if (is3DView()) {
auto pCamera = getCamera<SoOrthographicCamera* const>();
if (pCamera == nullptr)
if (!pCamera)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
navlib::box_t oldExtents;
@@ -399,13 +399,13 @@ long NavlibInterface::SetViewFOV(double)
long NavlibInterface::GetIsViewPerspective(navlib::bool_t& perspective) const
{
auto pPerspectiveCamera = getCamera<SoPerspectiveCamera* const>();
if (pPerspectiveCamera != nullptr) {
if (pPerspectiveCamera) {
perspective = true;
return 0;
}
auto pOrthographicCamera = getCamera<SoOrthographicCamera* const>();
if (pOrthographicCamera != nullptr || is2DView()) {
if (pOrthographicCamera || is2DView()) {
perspective = false;
return 0;
}
@@ -417,7 +417,7 @@ long NavlibInterface::GetModelExtents(navlib::box_t& extents) const
{
if (is3DView()) {
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
if (inventorViewer == nullptr)
if (!inventorViewer)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
SoGetBoundingBoxAction action(inventorViewer->getSoRenderManager()->getViewportRegion());
+9 -9
View File
@@ -71,7 +71,7 @@ long NavlibInterface::GetPivotPosition(navlib::point_t&) const
long NavlibInterface::SetPivotPosition(const navlib::point_t& position)
{
if (pivot.pTransform == nullptr)
if (!pivot.pTransform)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
pivot.pTransform->translation.setValue(position.x, position.y, position.z);
@@ -86,7 +86,7 @@ long NavlibInterface::IsUserPivot(navlib::bool_t& userPivot) const
long NavlibInterface::GetPivotVisible(navlib::bool_t& visible) const
{
if (pivot.pVisibility == nullptr)
if (!pivot.pVisibility)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
visible = pivot.pVisibility->whichChild.getValue() == SO_SWITCH_ALL;
@@ -96,7 +96,7 @@ long NavlibInterface::GetPivotVisible(navlib::bool_t& visible) const
long NavlibInterface::SetPivotVisible(bool visible)
{
if (pivot.pVisibility == nullptr)
if (!pivot.pVisibility)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
if (visible)
@@ -115,11 +115,11 @@ long NavlibInterface::GetHitLookAt(navlib::point_t& position) const
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
if (inventorViewer == nullptr)
if (!inventorViewer)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
SoNode* pSceneGraph = inventorViewer->getSceneGraph();
if (pSceneGraph == nullptr)
if (!pSceneGraph)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
// Prepare the ray-picking object
@@ -131,7 +131,7 @@ long NavlibInterface::GetHitLookAt(navlib::point_t& position) const
// Get the camera rotation
SoCamera* pCamera = getCamera<SoCamera*>();
if (pCamera == nullptr)
if (!pCamera)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
pCamera->orientation.getValue().getValue(cameraMatrix);
@@ -177,7 +177,7 @@ long NavlibInterface::GetHitLookAt(navlib::point_t& position) const
SoPickedPoint* pickedPoint = rayPickAction.getPickedPoint();
// Check if there was a hit
if (pickedPoint != nullptr) {
if (pickedPoint) {
SbVec3f hitPoint = pickedPoint->getPoint();
float distance = (origin - hitPoint).length();
@@ -213,7 +213,7 @@ long NavlibInterface::GetSelectionExtents(navlib::box_t& extents) const
Gui::ViewProvider* pViewProvider =
Gui::Application::Instance->getViewProvider(selection.pObject);
if (pViewProvider == nullptr)
if (!pViewProvider)
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
boundingBox.Add(pViewProvider->getBoundingBox(selection.SubName, true));
@@ -254,7 +254,7 @@ long NavlibInterface::SetHitLookFrom(const navlib::point_t& eye)
}
else {
auto pCamera = getCamera<SoCamera*>();
if (pCamera == nullptr) {
if (!pCamera) {
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
}
+76 -27
View File
@@ -227,7 +227,7 @@ struct ApplicationP
std::map<const App::Document*, Gui::Document*> documents;
/// Active document
Gui::Document* activeDocument {nullptr};
Gui::Document* editDocument {nullptr};
std::vector<Gui::Document*> editDocuments;
MacroManager* macroMngr;
PreferencePackManager* prefPackManager;
@@ -631,7 +631,7 @@ Application::Application(bool GUIenabled)
PyObject* module = PyImport_AddModule("FreeCADGui");
PyMethodDef* meth = FreeCADGui_methods;
PyObject* dict = PyModule_GetDict(module);
for (; meth->ml_name != nullptr; meth++) {
for (; meth->ml_name; meth++) {
PyObject* descr;
descr = PyCFunction_NewEx(meth, nullptr, nullptr);
if (!descr) {
@@ -876,10 +876,8 @@ void Application::importFrom(const char* FileName, const char* DocName, const ch
"User parameter:BaseApp/Preferences/View"
);
if (hGrp->GetBool("AutoFitToView", true)) {
MDIView* view = doc->getActiveView();
if (view) {
const char* ret = nullptr;
if (view->onMsg("ViewFit", &ret)) {
if (MDIView* view = doc->getActiveView()) {
if (view->onMsg("ViewFit")) {
updateActions(true);
}
}
@@ -1287,6 +1285,9 @@ void Application::slotActiveDocument(const App::Document& Doc)
Py::Module("FreeCADGui").setAttr(std::string("ActiveDocument"), Py::None());
}
}
if (!d->activeDocument->workbench().empty()) {
activateWorkbench(d->activeDocument->workbench().c_str());
}
// Update the application to show the unit change
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
@@ -1404,10 +1405,10 @@ void Application::onLastWindowClosed(Gui::Document* pcDoc)
}
/// send Messages to the active view
bool Application::sendMsgToActiveView(const char* pMsg, const char** ppReturn)
bool Application::sendMsgToActiveView(const char* pMsg)
{
MDIView* pView = getMainWindow()->activeWindow();
bool res = pView ? pView->onMsg(pMsg, ppReturn) : false;
bool res = pView ? pView->onMsg(pMsg) : false;
updateActions(true);
return res;
}
@@ -1419,7 +1420,7 @@ bool Application::sendHasMsgToActiveView(const char* pMsg)
}
/// send Messages to the active view
bool Application::sendMsgToFocusView(const char* pMsg, const char** ppReturn)
bool Application::sendMsgToFocusView(const char* pMsg)
{
MDIView* pView = getMainWindow()->activeWindow();
if (!pView) {
@@ -1427,7 +1428,7 @@ bool Application::sendMsgToFocusView(const char* pMsg, const char** ppReturn)
}
for (auto focus = qApp->focusWidget(); focus; focus = focus->parentWidget()) {
if (focus == pView) {
bool res = pView->onMsg(pMsg, ppReturn);
bool res = pView->onMsg(pMsg);
updateActions(true);
return res;
}
@@ -1493,26 +1494,65 @@ Gui::Document* Application::activeDocument() const
Gui::Document* Application::editDocument() const
{
return d->editDocument;
}
Gui::MDIView* Application::editViewOfNode(SoNode* node) const
{
return d->editDocument ? d->editDocument->getViewOfNode(node) : nullptr;
}
void Application::setEditDocument(Gui::Document* doc)
{
if (!doc) {
d->editDocument = nullptr;
if (d->editDocuments.empty()) {
return nullptr;
}
else if (doc == d->editDocument) {
return d->editDocuments[0];
}
Gui::Document* Application::editDocument(const std::function<bool(Gui::Document*)>& eval)
{
auto found = std::ranges::find_if(d->editDocuments, eval);
return found == d->editDocuments.end() ? nullptr : *found;
}
std::vector<Gui::Document*> Application::editDocuments() const
{
return d->editDocuments;
}
bool Application::isInEdit(Gui::Document* pcDocument) const
{
return std::ranges::find(d->editDocuments, pcDocument) != d->editDocuments.end();
}
void Application::unsetEditDocument(Gui::Document* pcDocument)
{
if (std::erase(d->editDocuments, pcDocument) == 0) {
return;
}
for (auto& v : d->documents) {
v.second->_resetEdit();
pcDocument->_resetEdit();
updateActions();
}
void Application::unsetEditDocumentIf(const std::function<bool(Gui::Document*)>& eval)
{
std::erase_if(d->editDocuments, [&](Gui::Document* doc) {
if (eval(doc)) {
doc->_resetEdit();
return true;
}
return false;
});
updateActions();
}
Gui::MDIView* Application::editViewOfNode(SoNode* node) const
{
for (auto editDoc : d->editDocuments) {
if (Gui::MDIView* view = editDoc->getViewOfNode(node)) {
return view;
}
}
d->editDocument = doc;
return nullptr;
}
void Application::setEditDocument(Gui::Document* pcDocument)
{
if (pcDocument == nullptr) {
return;
}
if (std::ranges::find(d->editDocuments, pcDocument) != d->editDocuments.end()) {
return;
}
d->editDocuments.push_back(pcDocument);
updateActions();
}
@@ -1533,12 +1573,18 @@ void Application::setActiveDocument(Gui::Document* pcDocument)
return;
}
}
if (d->activeDocument) {
d->activeDocument->setIsActive(false);
}
d->activeDocument = pcDocument;
std::string nameApp, nameGui;
// This adds just a line to the macro file but does not set the active document
// Macro recording of this is problematic, thus it's written out as comment.
if (pcDocument) {
pcDocument->setIsActive(true);
nameApp += "App.setActiveDocument(\"";
nameApp += pcDocument->getDocument()->getName();
nameApp += "\")\n";
@@ -1895,6 +1941,9 @@ bool Application::activateWorkbench(const char* name)
}
newWb->activated();
}
if (activeDocument()) {
activeDocument()->setWorkbench(name);
}
}
catch (Py::Exception&) {
Base::PyException e; // extract the Python error text
@@ -2792,7 +2841,7 @@ void Application::setStyle(const QString& name)
return qobject_cast<FreeCADStyle*>(style) != nullptr;
};
if (auto* current = qApp->style(); current != nullptr && requiresEventFilter(current)) {
if (auto* current = qApp->style(); current && requiresEventFilter(current)) {
qApp->removeEventFilter(current);
}
+19 -4
View File
@@ -91,11 +91,11 @@ public:
/** @name methods for View handling */
//@{
/// send Messages to the active view
bool sendMsgToActiveView(const char* pMsg, const char** ppReturn = nullptr);
bool sendMsgToActiveView(const char* pMsg);
/// send Messages test to the active view
bool sendHasMsgToActiveView(const char* pMsg);
/// send Messages to the focused view
bool sendMsgToFocusView(const char* pMsg, const char** ppReturn = nullptr);
bool sendMsgToFocusView(const char* pMsg);
/// send Messages test to the focused view
bool sendHasMsgToFocusView(const char* pMsg);
/// Attach a view (get called by the FCView constructor)
@@ -181,11 +181,26 @@ public:
Gui::Document* activeDocument() const;
/// Set the active document
void setActiveDocument(Gui::Document* pcDocument);
/// Getter for the editing document
/// Getter for the editing document, will be removed soon
Gui::Document* editDocument() const;
/// Getter for the first editing document that matches a functor
Gui::Document* editDocument(const std::function<bool(Gui::Document*)>& eval);
/// Getter for all currently editing documents, all pointers are guaranteed to be non-null
std::vector<Gui::Document*> editDocuments() const;
// Returns true if the document is in edit (will make more sense once the edit document it is a
// vector)
bool isInEdit(Gui::Document* pcDocument) const;
// Reset edit if eval returns true for a document in edit
Gui::MDIView* editViewOfNode(SoNode* node) const;
/// Set editing document, which will reset editing of all other document
/// Adds a document in edit
void setEditDocument(Gui::Document* pcDocument);
// After this, isInEdit(pcDocument) returns false
void unsetEditDocument(Gui::Document* pcDocument);
void unsetEditDocumentIf(const std::function<bool(Gui::Document*)>& eval);
/** Retrieves a pointer to the Gui::Document whose App::Document has the name \a name.
* If no such document exists 0 is returned.
*/
+4 -16
View File
@@ -253,7 +253,7 @@ PyMethodDef ApplicationPy::Methods[] = {
{"SendMsgToActiveView",
(PyCFunction)ApplicationPy::sSendActiveView,
METH_VARARGS,
"SendMsgToActiveView(name, suppress=False) -> str or None\n"
"SendMsgToActiveView(name, suppress=False) -> None\n"
"\n"
"Send message to the active view. Deprecated, use class View.\n"
"\n"
@@ -262,7 +262,7 @@ PyMethodDef ApplicationPy::Methods[] = {
{"sendMsgToFocusView",
(PyCFunction)ApplicationPy::sSendFocusView,
METH_VARARGS,
"sendMsgToFocusView(name, suppress=False) -> str or None\n"
"sendMsgToFocusView(name, suppress=False) -> None\n"
"\n"
"Send message to the focused view.\n"
"\n"
@@ -928,18 +928,12 @@ PyObject* ApplicationPy::sSendActiveView(PyObject* /*self*/, PyObject* args)
return nullptr;
}
const char* ppReturn = nullptr;
if (!Application::Instance->sendMsgToActiveView(psCommandStr, &ppReturn)) {
if (!Application::Instance->sendMsgToActiveView(psCommandStr)) {
if (!Base::asBoolean(suppress)) {
Base::Console().warning("Unknown view command: %s\n", psCommandStr);
}
}
// Print the return value to the output
if (ppReturn) {
return Py_BuildValue("s", ppReturn);
}
Py_Return;
}
@@ -952,18 +946,12 @@ PyObject* ApplicationPy::sSendFocusView(PyObject* /*self*/, PyObject* args)
return nullptr;
}
const char* ppReturn = nullptr;
if (!Application::Instance->sendMsgToFocusView(psCommandStr, &ppReturn)) {
if (!Application::Instance->sendMsgToFocusView(psCommandStr)) {
if (!Base::asBoolean(suppress)) {
Base::Console().warning("Unknown view command: %s\n", psCommandStr);
}
}
// Print the return value to the output
if (ppReturn) {
return Py_BuildValue("s", ppReturn);
}
Py_Return;
}
+3
View File
@@ -490,6 +490,7 @@ SET(Gui_UIC_SRCS
InputVector.ui
Placement.ui
TaskTransform.ui
TaskCommandLink.ui
TextureMapping.ui
TaskView/TaskAppearance.ui
TaskView/TaskOrientation.ui
@@ -578,6 +579,7 @@ SET(Dialog_CPP_SRCS
TaskDlgRelocation.cpp
Dialogs/DlgCheckableMessageBox.cpp
TaskTransform.cpp
TaskCommandLink.cpp
Dialogs/DlgUndoRedo.cpp
InputVector.cpp
Placement.cpp
@@ -620,6 +622,7 @@ SET(Dialog_HPP_SRCS
Dialogs/DlgVersionMigrator.h
TaskDlgRelocation.h
TaskTransform.h
TaskCommandLink.h
Dialogs/DlgUndoRedo.h
InputVector.h
Placement.h
+1 -1
View File
@@ -226,7 +226,7 @@ QMap<QString, CallTip> CallTipsList::extractTips(const QString& context) const
try {
PyErr_Clear();
Py::Module module("__main__");
if (module.ptr() == nullptr) {
if (!module.ptr()) {
return tips;
}
Py::Dict dict = module.getDict();
+27 -4
View File
@@ -21,6 +21,7 @@
***************************************************************************/
#include <limits>
#include <functional>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/nodes/SoClipPlane.h>
@@ -29,6 +30,8 @@
#include <QDockWidget>
#include <QPointer>
#include <App/Application.h>
#include "Clipping.h"
#include "ui_Clipping.h"
#include "DockWindowManager.h"
@@ -51,6 +54,10 @@ public:
bool flipY {false};
bool flipZ {false};
SoTimerSensor* sensor;
App::Document* shownOn {nullptr};
QDockWidget* dockWidget {nullptr};
fastsignals::scoped_connection activeDocConnection;
Private()
{
clipX = new SoClipPlane();
@@ -99,7 +106,7 @@ public:
/* TRANSLATOR Gui::Dialog::Clipping */
Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
Clipping::Clipping(Gui::View3DInventor* view, App::Document* showOn, QWidget* parent)
: QDialog(parent)
, d(new Private)
{
@@ -124,6 +131,7 @@ Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
d->ui.dirZ->setRange(-max, max);
d->ui.dirZ->setSingleStep(0.1f);
d->ui.dirZ->setValue(1.0f);
d->shownOn = showOn;
d->view = view;
View3DInventorViewer* viewer = view->getViewer();
@@ -190,14 +198,15 @@ Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
}
}
Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view)
Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view, App::Document* showOn)
{
// embed this dialog into a QDockWidget
auto clipping = new Clipping(view);
auto clipping = new Clipping(view, showOn);
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
QDockWidget* dw = pDockMgr->addDockWindow("Clipping", clipping, Qt::LeftDockWidgetArea);
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
dw->show();
clipping->d->dockWidget = dw;
return clipping;
}
@@ -205,6 +214,7 @@ Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view)
/** Destroys the object and frees any allocated resources */
Clipping::~Clipping()
{
d->activeDocConnection.disconnect();
d->node->removeChild(d->clipX);
d->node->removeChild(d->clipY);
d->node->removeChild(d->clipZ);
@@ -216,6 +226,8 @@ Clipping::~Clipping()
void Clipping::setupConnections()
{
// clang-format off
d->activeDocConnection = App::GetApplication().signalActiveDocument.connect(
std::bind(&Clipping::onActiveDocument, this, std::placeholders::_1));
connect(d->ui.groupBoxX, &QGroupBox::toggled,
this, &Clipping::onGroupBoxXToggled);
connect(d->ui.groupBoxY, &QGroupBox::toggled,
@@ -259,7 +271,18 @@ void Clipping::reject()
dw->deleteLater();
}
}
void Clipping::onActiveDocument(const App::Document& doc)
{
if (!d || !d->dockWidget) {
return;
}
if (&doc == d->shownOn) {
d->dockWidget->show();
}
else {
d->dockWidget->hide();
}
}
void Clipping::onGroupBoxXToggled(bool on)
{
if (on) {
+8 -2
View File
@@ -26,6 +26,11 @@
#include <QDialog>
#include <FCGlobal.h>
namespace App
{
class Document;
}
namespace Gui
{
class View3DInventor;
@@ -40,12 +45,13 @@ class GuiExport Clipping: public QDialog
Q_OBJECT
public:
static Clipping* makeDockWidget(Gui::View3DInventor*);
Clipping(Gui::View3DInventor* view, QWidget* parent = nullptr);
static Clipping* makeDockWidget(Gui::View3DInventor*, App::Document* showOn);
Clipping(Gui::View3DInventor* view, App::Document* showOn, QWidget* parent = nullptr);
~Clipping() override;
protected:
void setupConnections();
void onActiveDocument(const App::Document& doc);
void onGroupBoxXToggled(bool);
void onGroupBoxYToggled(bool);
void onGroupBoxZToggled(bool);
+59 -24
View File
@@ -36,7 +36,7 @@
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/AutoTransaction.h>
#include <App/Transactions.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Interpreter.h>
@@ -45,6 +45,7 @@
#include "Command.h"
#include "Action.h"
#include "App/Application.h"
#include "Application.h"
#include "BitmapFactory.h"
#include "Control.h"
@@ -447,11 +448,6 @@ void Command::invoke(int i, TriggerSource trigger)
void Command::_invoke(int id, bool disablelog)
{
try {
// Because Transaction now captures ViewObject changes, auto named
// transaction is disabled here to avoid too many unnecessary transactions.
//
App::AutoTransaction committer(nullptr, true);
// set the application module type for the macro
getGuiApplication()->macroManager()->setModule(sAppModule);
@@ -463,7 +459,6 @@ void Command::_invoke(int id, bool disablelog)
// check if it really works NOW (could be a delay between click deactivation of the button)
if (isActive()) {
auto manager = getGuiApplication()->macroManager();
auto editDoc = getGuiApplication()->editDocument();
if (!logdisabler) {
activated(id);
@@ -499,12 +494,10 @@ void Command::_invoke(int id, bool disablelog)
}
getMainWindow()->updateActions();
// If this command starts an editing, let the transaction persist
if (!editDoc && getGuiApplication()->editDocument()) {
committer.setEnable(false);
}
}
// here we assume that the overriden activated() function
// commited, aborted or gave the transaction id to a dialog
currentTransactionID = App::NullTransaction; // Get ready for next invoke
}
catch (const Base::SystemExitException&) {
throw;
@@ -550,9 +543,10 @@ void Command::testActive()
if (!(eType & ForEdit)) { // special case for commands which are only in some edit modes active
if ((!Gui::Control().isAllowedAlterDocument() && eType & AlterDoc)
|| (!Gui::Control().isAllowedAlterView() && eType & Alter3DView)
|| (!Gui::Control().isAllowedAlterSelection() && eType & AlterSelection)) {
App::Document* doc = getDocument();
if ((!Gui::Control().isAllowedAlterDocument(doc) && eType & AlterDoc)
|| (!Gui::Control().isAllowedAlterView(doc) && eType & Alter3DView)
|| (!Gui::Control().isAllowedAlterSelection(doc) && eType & AlterSelection)) {
_pcAction->setEnabled(false);
return;
}
@@ -681,27 +675,68 @@ QString Command::translatedGroupName() const
* operation default is the Command name.
* @see CommitCommand(),AbortCommand()
*/
void Command::openCommand(const char* sCmdName)
int Command::openCommand(App::TransactionName name)
{
if (!sCmdName) {
sCmdName = "Command";
currentTransactionID = openActiveDocumentCommand(name);
return currentTransactionID;
}
int Command::openCommand(std::string name)
{
return openCommand(App::TransactionName {.name = name, .temporary = false});
}
int Command::openActiveDocumentCommand(App::TransactionName name, int tid)
{
if (Gui::Document* guidoc = getGuiApplication()->activeDocument()) {
return guidoc->getDocument()->setActiveTransaction(name, tid);
}
App::GetApplication().setActiveTransaction(sCmdName);
return 0;
}
int Command::openActiveDocumentCommand(std::string name, int tid)
{
return openActiveDocumentCommand(App::TransactionName {.name = name, .temporary = false}, tid);
}
void Command::rename(const std::string& name)
{
App::GetApplication().setTransactionName(
currentTransactionID,
App::TransactionName {.name = name, .temporary = false}
);
}
void Command::commitCommand()
{
App::GetApplication().closeActiveTransaction();
commitCommand(currentTransactionID);
currentTransactionID = App::NullTransaction;
}
void Command::commitCommand(int tid)
{
if (tid != App::NullTransaction) {
App::GetApplication().commitTransaction(tid);
}
}
void Command::abortCommand()
{
App::GetApplication().closeActiveTransaction(true);
abortCommand(currentTransactionID);
currentTransactionID = App::NullTransaction;
}
void Command::abortCommand(int tid)
{
if (tid != App::NullTransaction) {
App::GetApplication().abortTransaction(tid);
}
}
int Command::transactionID() const
{
return currentTransactionID;
}
void Command::resetTransactionID()
{
currentTransactionID = App::NullTransaction;
}
bool Command::hasPendingCommand()
{
return !!App::GetApplication().getActiveTransaction();
App::Document* doc = App::GetApplication().getActiveDocument();
return doc && doc->getBookedTransactionID() != App::NullTransaction;
}
bool Command::_blockCmd = false;
+18 -3
View File
@@ -473,11 +473,24 @@ public:
/** @name Helper methods for the Undo/Redo and Update handling */
//@{
/// Open a new Undo transaction on the active document
static void openCommand(const char* sName = nullptr);
int openCommand(App::TransactionName name);
int openCommand(std::string name);
static int openActiveDocumentCommand(App::TransactionName name, int tid = App::NullTransaction);
static int openActiveDocumentCommand(std::string name, int tid = App::NullTransaction);
void rename(const std::string& name);
/// Commit the Undo transaction on the active document
static void commitCommand();
void commitCommand();
static void commitCommand(int tid);
/// Abort the Undo transaction on the active document
static void abortCommand();
void abortCommand();
static void abortCommand(int tid);
int transactionID() const;
void resetTransactionID();
/// Check if an Undo transaction is open on the active document
static bool hasPendingCommand();
/// Updates the (active) document (propagate changes)
@@ -715,6 +728,8 @@ protected:
/// Indicate if the command shall log to MacroManager
bool bCanLog;
//@}
int currentTransactionID {0}; // TransactionID created in _invoke
private:
static int _busy;
bool bEnabled;
+51 -26
View File
@@ -710,6 +710,7 @@ StdCmdNew::StdCmdNew()
sStatusTip = sToolTipText;
sPixmap = "document-new";
sAccel = keySequenceToAccel(QKeySequence::New);
eType = NoTransaction;
}
void StdCmdNew::activated(int iMsg)
@@ -1446,41 +1447,57 @@ void StdCmdDelete::activated(int iMsg)
{
Q_UNUSED(iMsg);
std::set<App::Document*> docs;
int tid = 0;
try {
openCommand(QT_TRANSLATE_NOOP("Command", "Delete"));
std::set<App::Document*> docs;
std::vector<App::TransactionLocker> tlocks;
auto manageDocCommand = [&tid, &tlocks](App::Document* doc) {
// The tid will not be updated if non-zero
tid = doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Delete"), tid);
tlocks.emplace_back(doc);
};
if (getGuiApplication()->sendHasMsgToFocusView(getName())) {
commitCommand();
// no command has been opened yet so we can skip this commit
// commitCommand();
return;
}
App::TransactionLocker tlock;
// Ensure that the document from which we send the command
// can undo it (e.g delete a subobject of an assembly
// from the assembly file)
manageDocCommand(getActiveGuiDocument()->getDocument());
Gui::getMainWindow()->setUpdatesEnabled(false);
auto editDoc = Application::Instance->editDocument();
ViewProviderDocumentObject* vpedit = nullptr;
if (editDoc) {
vpedit = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
}
if (vpedit && !vpedit->acceptDeletionsInEdit()) {
for (auto& sel : Selection().getSelectionEx(editDoc->getDocument()->getName())) {
if (sel.getObject() == vpedit->getObject()) {
if (!sel.getSubNames().empty()) {
vpedit->onDelete(sel.getSubNames());
docs.insert(editDoc->getDocument());
bool deletedSelectionOfEditDocument = false;
std::vector<Gui::Document*> editDocs = Application::Instance->editDocuments();
for (auto& editDoc : editDocs) {
auto vpedit = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
// In practice, no ViewProviderDocumentObject accepts deletion in edit - 2025-06-17
if (vpedit && !vpedit->acceptDeletionsInEdit()) {
for (auto& sel : Selection().getSelectionEx(editDoc->getDocument()->getName())) {
if (sel.getObject() == vpedit->getObject()) {
if (!sel.getSubNames().empty()) {
deletedSelectionOfEditDocument = true;
manageDocCommand(editDoc->getDocument());
vpedit->onDelete(sel.getSubNames());
docs.insert(editDoc->getDocument());
}
break;
}
break;
}
}
}
else {
if (!deletedSelectionOfEditDocument) {
std::set<QString> affectedLabels;
bool more = false;
auto sels = Selection().getSelectionEx();
bool autoDeletion = true;
for (auto& sel : sels) {
auto obj = sel.getObject();
if (obj == nullptr) {
if (!obj) {
Base::Console().developerWarning(
"StdCmdDelete::activated",
"App::DocumentObject pointer is nullptr\n"
@@ -1548,6 +1565,7 @@ void StdCmdDelete::activated(int iMsg)
auto obj = sel.getObject();
Gui::ViewProvider* vp = Application::Instance->getViewProvider(obj);
if (vp) {
manageDocCommand(obj->getDocument());
// ask the ViewProvider if it wants to do some clean up
if (vp->onDelete(sel.getSubNames())) {
docs.insert(obj->getDocument());
@@ -1581,6 +1599,8 @@ void StdCmdDelete::activated(int iMsg)
QString::fromLatin1(e.what())
);
e.reportException();
App::GetApplication().abortTransaction(tid);
tid = 0;
}
catch (...) {
QMessageBox::critical(
@@ -1588,8 +1608,11 @@ void StdCmdDelete::activated(int iMsg)
QObject::tr("Delete Failed"),
QStringLiteral("Unknown error")
);
App::GetApplication().abortTransaction(tid);
tid = 0;
}
commitCommand();
App::GetApplication().commitTransaction(tid);
Gui::getMainWindow()->setUpdatesEnabled(true);
Gui::getMainWindow()->update();
}
@@ -1635,7 +1658,8 @@ void StdCmdRefresh::activated([[maybe_unused]] int iMsg)
return;
}
App::AutoTransaction trans((eType & NoTransaction) ? nullptr : "Recompute");
App::AutoTransaction trans((eType & NoTransaction) ? 0 : openActiveDocumentCommand("Recompute"));
try {
doCommand(Doc, "App.activeDocument().recompute(None,True,True)");
}
@@ -1738,7 +1762,7 @@ void StdCmdPlacement::activated(int iMsg)
plm->clearSelection();
}
}
Gui::Control().showDialog(plm);
Gui::Control().showDialog(plm, getDocument());
}
bool StdCmdPlacement::isActive()
@@ -2133,9 +2157,10 @@ protected:
return;
}
openCommand(QT_TRANSLATE_NOOP("Command", "Paste expressions"));
int tid = App::NullTransaction;
try {
for (auto& v : exprs) {
tid = v.first->openTransaction(QT_TRANSLATE_NOOP("Command", "Paste expressions"), tid);
for (auto& v2 : v.second) {
auto& expressions = v2.second;
auto old = v2.first->getExpressions();
@@ -2152,13 +2177,13 @@ protected:
}
}
}
commitCommand();
App::GetApplication().commitTransaction(tid);
}
catch (const Base::Exception& e) {
abortCommand();
App::GetApplication().abortTransaction(tid);
QMessageBox::critical(
getMainWindow(),
QObject::tr("Failed to Paste Expressions"),
QObject::tr("Failed to paste expressions"),
QString::fromLatin1(e.what())
);
e.reportException();
+2 -2
View File
@@ -330,7 +330,7 @@ Gui::Action* StdCmdToggleSkipRecompute::createAction()
void StdCmdToggleSkipRecompute::activated(int iMsg)
{
const auto doc = this->getDocument();
if (doc == nullptr) {
if (!doc) {
return;
}
@@ -345,7 +345,7 @@ void StdCmdToggleSkipRecompute::activated(int iMsg)
bool StdCmdToggleSkipRecompute::isActive()
{
const auto doc = this->getDocument();
if (doc == nullptr) {
if (!doc) {
return false;
}
+62 -44
View File
@@ -34,9 +34,11 @@
#include "Action.h"
#include "Application.h"
#include "Command.h"
#include "Control.h"
#include "Document.h"
#include "MainWindow.h"
#include "Selection.h"
#include "TaskCommandLink.h"
#include "Tree.h"
#include "ViewProviderDocumentObject.h"
#include "WaitCursor.h"
@@ -280,6 +282,51 @@ void StdCmdLinkMake::activated(int)
return;
}
auto exec = [=](std::vector<App::DocumentObject*> objs) {
doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Make link"));
try {
if (objs.empty()) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s')",
doc->getName(),
name.c_str()
);
Selection().addSelection(doc->getName(), name.c_str());
}
else {
for (auto obj : objs) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument("
"'%s'"
").%s)",
doc->getName(),
name.c_str(),
obj->getDocument()->getName(),
obj->getNameInDocument()
);
setLinkLabel(obj, doc->getName(), name.c_str());
Selection().addSelection(doc->getName(), name.c_str());
}
}
Selection().selStackPush();
doc->commitTransaction();
}
catch (const Base::Exception& e) {
doc->abortTransaction();
QMessageBox::critical(
getMainWindow(),
QObject::tr("Create link failed"),
QString::fromLatin1(e.what())
);
e.reportException();
}
};
std::set<App::DocumentObject*> objs;
for (auto& sel : Selection().getCompleteSelection()) {
if (sel.pObject && sel.pObject->isAttachedToDocument()) {
@@ -287,48 +334,13 @@ void StdCmdLinkMake::activated(int)
}
}
Selection().selStackPush();
Selection().clearCompleteSelection();
Command::openCommand(QT_TRANSLATE_NOOP("Command", "Make link"));
try {
if (objs.empty()) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s')",
doc->getName(),
name.c_str()
);
Selection().addSelection(doc->getName(), name.c_str());
}
else {
for (auto obj : objs) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument('%s'"
").%s)",
doc->getName(),
name.c_str(),
obj->getDocument()->getName(),
obj->getNameInDocument()
);
setLinkLabel(obj, doc->getName(), name.c_str());
Selection().addSelection(doc->getName(), name.c_str());
}
}
Selection().selStackPush();
Command::commitCommand();
if (objs.empty()) {
Gui::Control().showDialog(new TaskCommandLinkDialog(exec));
}
catch (const Base::Exception& e) {
Command::abortCommand();
QMessageBox::critical(
getMainWindow(),
QObject::tr("Create link failed"),
QString::fromLatin1(e.what())
);
e.reportException();
else {
Selection().selStackPush();
Selection().clearCompleteSelection();
exec(std::vector<App::DocumentObject*>(objs.begin(), objs.end()));
}
}
@@ -476,7 +488,7 @@ static void linkConvert(bool unlink)
// now, do actual operation
const char* transactionName = unlink ? "Unlink" : "Replace with link";
Command::openCommand(transactionName);
int tid = 0;
try {
std::unordered_map<App::DocumentObject*, App::DocumentObjectT> recomputeSet;
for (auto& v : infos) {
@@ -493,6 +505,12 @@ static void linkConvert(bool unlink)
recomputeSet.emplace(parent, parent);
}
auto doc = parent->getDocument();
tid = doc->openTransaction(
App::TransactionName {.name = transactionName, .temporary = false},
tid
);
App::DocumentObject* replaceObj;
if (unlink) {
replaceObj = obj->getLinkedObject(false);
@@ -552,10 +570,10 @@ static void linkConvert(bool unlink)
recomputes.front()->getDocument()->recompute(recomputes);
}
Command::commitCommand();
App::GetApplication().commitTransaction(tid);
}
catch (const Base::Exception& e) {
Command::abortCommand();
App::GetApplication().abortTransaction(tid);
auto title = unlink ? QObject::tr("Unlink failed") : QObject::tr("Replace link failed");
QMessageBox::critical(getMainWindow(), title, QString::fromLatin1(e.what()));
e.reportException();
+2
View File
@@ -101,6 +101,8 @@ void StdCmdPart::activated(int iMsg)
PartName.c_str()
);
commitCommand();
updateActive();
}
+112 -42
View File
@@ -21,6 +21,9 @@
***************************************************************************/
#include <sstream>
#include <vector>
#include <tuple>
#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
@@ -393,8 +396,15 @@ void StdCmdFreezeViews::activated(int iMsg)
}
else if (iMsg == 3) {
// Create a new view
const char* ppReturn = nullptr;
getGuiApplication()->sendMsgToActiveView("GetCamera", &ppReturn);
auto* view3d = freecad_cast<View3DInventor*>(getGuiApplication()->activeView());
if (!view3d) {
Base::Console().developerError(
"StdCmdFreezeViews",
"Expected the active view to be View3DInventor\n"
);
return;
}
const std::string& camera = view3d->getCamera();
QList<QAction*> acts = pcAction->actions();
int index = 1;
@@ -403,7 +413,7 @@ void StdCmdFreezeViews::activated(int iMsg)
savedViews++;
QString viewnr = QString(QObject::tr("Restore View &%1")).arg(index);
(*it)->setText(viewnr);
(*it)->setToolTip(QString::fromLatin1(ppReturn));
(*it)->setToolTip(QString::fromStdString(camera));
(*it)->setVisible(true);
if (index < 10) {
(*it)->setShortcut(QKeySequence(QStringLiteral("CTRL+%1").arg(index)));
@@ -423,11 +433,28 @@ void StdCmdFreezeViews::activated(int iMsg)
// Activate a view
QList<QAction*> acts = pcAction->actions();
QString data = acts[iMsg]->toolTip();
QString send = QStringLiteral("SetCamera %1").arg(data);
getGuiApplication()->sendMsgToActiveView(send.toLatin1());
MDIView* view = getGuiApplication()->activeView();
if (auto* view3D = freecad_cast<View3DInventor*>(view)) {
view3D->setCamera(data.toStdString().c_str());
}
}
}
bool StdCmdFreezeViews::isActive()
{
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
separator->setVisible(savedViews > 0);
if (view) {
saveView->setEnabled(savedViews > 0);
freezeView->setEnabled(savedViews < maxViews);
clearView->setEnabled(savedViews > 0);
return true;
}
return false;
}
void StdCmdFreezeViews::onSaveViews()
{
// Save the views to an XML file
@@ -589,23 +616,6 @@ void StdCmdFreezeViews::onRestoreViews()
}
}
bool StdCmdFreezeViews::isActive()
{
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
if (view) {
saveView->setEnabled(savedViews > 0);
freezeView->setEnabled(savedViews < maxViews);
clearView->setEnabled(savedViews > 0);
separator->setVisible(savedViews > 0);
return true;
}
else {
separator->setVisible(savedViews > 0);
}
return false;
}
void StdCmdFreezeViews::languageChange()
{
Command::languageChange();
@@ -633,7 +643,34 @@ void StdCmdFreezeViews::languageChange()
// Std_ToggleClipPlane
//===========================================================================
DEF_STD_CMD_AC(StdCmdToggleClipPlane)
class StdCmdToggleClipPlane: public Gui::Command
{
public:
StdCmdToggleClipPlane();
virtual ~StdCmdToggleClipPlane()
{}
virtual const char* className() const
{
return "StdCmdToggleClipPlane";
}
protected:
virtual void activated(int iMsg);
virtual bool isActive(void);
virtual Gui::Action* createAction(void);
private:
StdCmdToggleClipPlane(const StdCmdToggleClipPlane&) = delete;
StdCmdToggleClipPlane(StdCmdToggleClipPlane&&) = delete;
StdCmdToggleClipPlane& operator=(const StdCmdToggleClipPlane&) = delete;
StdCmdToggleClipPlane& operator=(StdCmdToggleClipPlane&&) = delete;
void garbageCollect();
bool hasClipping(App::Document* doc) const;
private:
std::vector<std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>> clippings;
};
StdCmdToggleClipPlane::StdCmdToggleClipPlane()
: Command("Std_ToggleClipPlane")
@@ -656,11 +693,15 @@ Action* StdCmdToggleClipPlane::createAction()
void StdCmdToggleClipPlane::activated(int iMsg)
{
Q_UNUSED(iMsg);
static QPointer<Gui::Dialog::Clipping> clipping = nullptr;
if (!clipping) {
App::Document* doc = getActiveDocument();
if (!doc) {
return;
}
garbageCollect(); // remove dead pointers
if (!hasClipping(doc)) {
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
if (view) {
clipping = Gui::Dialog::Clipping::makeDockWidget(view);
clippings.push_back(std::make_pair(doc, Gui::Dialog::Clipping::makeDockWidget(view, doc)));
}
}
}
@@ -671,6 +712,31 @@ bool StdCmdToggleClipPlane::isActive()
return view ? true : false;
}
void StdCmdToggleClipPlane::garbageCollect()
{
// We assume the vector to be small
std::vector<std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>> newClippings;
newClippings.reserve(clippings.size());
std::copy_if(
clippings.begin(),
clippings.end(),
std::back_inserter(newClippings),
[](const std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>& clipPair) -> bool {
return clipPair.second != nullptr;
}
);
clippings = newClippings;
}
bool StdCmdToggleClipPlane::hasClipping(App::Document* doc) const
{
return std::ranges::find(
clippings,
doc,
&std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>::first
)
!= clippings.end();
}
//===========================================================================
// StdCmdDrawStyle
//===========================================================================
@@ -2630,34 +2696,38 @@ StdCmdViewIvIssueCamPos::StdCmdViewIvIssueCamPos()
void StdCmdViewIvIssueCamPos::activated(int iMsg)
{
Q_UNUSED(iMsg);
std::string Temp, Temp2;
std::string::size_type pos;
const char* ppReturn = nullptr;
getGuiApplication()->sendMsgToActiveView("GetCamera", &ppReturn);
auto* view3d = freecad_cast<View3DInventor*>(getGuiApplication()->activeView());
if (!view3d) {
Base::Console().developerError(
"StdCmdViewIvIssueCameraPos",
"Expected the active view to be View3DInventor\n"
);
return;
}
std::string camera = view3d->getCamera();
// remove the #inventor line...
Temp2 = ppReturn;
pos = Temp2.find_first_of("\n");
Temp2.erase(0, pos);
std::string::size_type pos = camera.find_first_of('\n');
camera.erase(0, pos);
// remove all returns
while ((pos = Temp2.find('\n')) != std::string::npos) {
Temp2.replace(pos, 1, " ");
while ((pos = camera.find('\n')) != std::string::npos) {
camera.replace(pos, 1, " ");
}
// build up the command string
Temp += "Gui.SendMsgToActiveView(\"SetCamera ";
Temp += Temp2;
Temp += "\")";
std::string command = "Gui.activeView().setCamera(\"";
command += camera;
command += "\")";
Base::Console().message("%s\n", Temp2.c_str());
getGuiApplication()->macroManager()->addLine(MacroManager::Gui, Temp.c_str());
Base::Console().message("%s\n", camera.c_str());
getGuiApplication()->macroManager()->addLine(MacroManager::Gui, command.c_str());
}
bool StdCmdViewIvIssueCamPos::isActive()
{
return getGuiApplication()->sendHasMsgToActiveView("GetCamera");
return freecad_cast<View3DInventor*>(getGuiApplication()->activeView());
}
@@ -3420,7 +3490,7 @@ StdCmdTextureMapping::StdCmdTextureMapping()
void StdCmdTextureMapping::activated(int iMsg)
{
Q_UNUSED(iMsg);
Gui::Control().showDialog(new Gui::Dialog::TaskTextureMapping);
Gui::Control().showDialog(new Gui::Dialog::TaskTextureMapping, getDocument());
}
bool StdCmdTextureMapping::isActive()
+110 -51
View File
@@ -26,10 +26,12 @@
#include <QDockWidget>
#include <QPointer>
#include <App/AutoTransaction.h>
#include <App/Document.h>
#include <Gui/Application.h>
#include <Gui/ComboView.h>
#include <Gui/DockWindowManager.h>
#include <Gui/MainWindow.h>
#include <Gui/Document.h>
#include "Control.h"
#include "BitmapFactory.h"
@@ -45,8 +47,7 @@ using namespace std;
ControlSingleton* ControlSingleton::_pcSingleton = nullptr;
ControlSingleton::ControlSingleton()
: ActiveDialog(nullptr)
, oldTabIndex(-1)
: oldTabIndex(-1)
{}
ControlSingleton::~ControlSingleton() = default;
@@ -145,14 +146,28 @@ void ControlSingleton::showModelView()
}
}
void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg)
void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg, App::Document* attachTo)
{
attachTo = docOrDefault(attachTo);
if (!attachTo) {
qWarning() << "ControlSingleton::showDialog: Cannot attach to nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
// should return the pointer to combo view
if (!taskView) {
return;
}
// only one dialog at a time, print a warning instead of raising an assert
if (ActiveDialog && ActiveDialog != dlg) {
TaskView::TaskDialog* foundDialog = taskView->dialog(attachTo);
if (!dlg || foundDialog == dlg) {
if (dlg) {
qWarning() << "ControlSingleton::showDialog: Can't show "
<< dlg->metaObject()->className()
<< " since there is already an active task dialog";
<< " since there is already an active task dialog in Document "
<< (attachTo ? attachTo->getName() : "''");
}
else {
qWarning() << "ControlSingleton::showDialog: Task dialog is null";
@@ -160,69 +175,87 @@ void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg)
return;
}
// Since the caller sets up a modeless task panel, it indicates intention
// for prolonged editing. So disable auto transaction in the current call
// stack.
// Do this before showing the dialog because its open() function is called
// which may open a transaction but fails when auto transaction is still active.
App::AutoTransaction::setEnable(false);
bool addedDialog = taskView->showDialog(dlg, attachTo);
Gui::TaskView::TaskView* taskView = taskPanel();
// should return the pointer to combo view
if (taskView) {
taskView->showDialog(dlg);
// make sure that the combo view is shown
auto dw = qobject_cast<QDockWidget*>(taskView->parentWidget());
if (dw) {
aboutToShowDialog(dw);
dw->setVisible(true);
dw->toggleViewAction()->setVisible(true);
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
}
if (ActiveDialog == dlg) {
return; // dialog is already defined
}
ActiveDialog = dlg;
connect(dlg, &TaskView::TaskDialog::aboutToBeDestroyed, this, &ControlSingleton::closedDialog);
// make sure that the combo view is shown
if (auto dw = qobject_cast<QDockWidget*>(taskView->parentWidget())) {
aboutToShowDialog(dw);
dw->setVisible(true);
dw->toggleViewAction()->setVisible(true);
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
}
if (!addedDialog) {
return; // dialog is already defined
}
connect(dlg, &TaskView::TaskDialog::aboutToBeDestroyed, this, [this, attachTo] {
closedDialog(attachTo);
});
}
Gui::TaskView::TaskDialog* ControlSingleton::activeDialog() const
Gui::TaskView::TaskDialog* ControlSingleton::activeDialog(App::Document* attachedTo) const
{
return ActiveDialog;
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return nullptr;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
return taskView->dialog(attachedTo);
}
return nullptr;
}
void ControlSingleton::accept()
void ControlSingleton::accept(App::Document* attachedTo)
{
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
qWarning() << "ControlSingleton::accept: Cannot accept dialog of nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
taskView->accept();
taskView->accept(attachedTo);
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
}
}
void ControlSingleton::reject()
void ControlSingleton::reject(App::Document* attachedTo)
{
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
qWarning() << "ControlSingleton::reject: Cannot reject dialog of nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
taskView->reject();
taskView->reject(attachedTo);
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
}
}
void ControlSingleton::closeDialog()
void ControlSingleton::closeDialog(App::Document* attachedTo)
{
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
qWarning() << "ControlSingleton::closeDialog: Cannot close dialog of nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
taskView->removeDialog();
taskView->removeDialog(attachedTo);
}
}
void ControlSingleton::closedDialog()
void ControlSingleton::closedDialog(App::Document* attachedTo)
{
ActiveDialog = nullptr;
Gui::TaskView::TaskView* taskView = taskPanel();
assert(taskView);
@@ -237,31 +270,57 @@ void ControlSingleton::closedDialog()
}
}
bool ControlSingleton::isAllowedAlterDocument() const
bool ControlSingleton::isAllowedAlterDocument(App::Document* attachedTo) const
{
if (ActiveDialog) {
return ActiveDialog->isAllowedAlterDocument();
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return true;
}
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
if (dlg) {
return dlg->isAllowedAlterDocument();
}
return true;
}
bool ControlSingleton::isAllowedAlterView() const
bool ControlSingleton::isAllowedAlterView(App::Document* attachedTo) const
{
if (ActiveDialog) {
return ActiveDialog->isAllowedAlterView();
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return true;
}
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
if (dlg) {
return dlg->isAllowedAlterView();
}
return true;
}
bool ControlSingleton::isAllowedAlterSelection() const
bool ControlSingleton::isAllowedAlterSelection(App::Document* attachedTo) const
{
if (ActiveDialog) {
return ActiveDialog->isAllowedAlterSelection();
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return true;
}
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
if (dlg) {
return dlg->isAllowedAlterSelection();
}
return true;
}
App::Document* ControlSingleton::docOrDefault(App::Document* attachedTo)
{
if (!attachedTo && Application::Instance->activeDocument()) {
attachedTo = Application::Instance->activeDocument()->getDocument();
}
return attachedTo;
}
// -------------------------------------------
ControlSingleton& ControlSingleton::instance()
+16 -11
View File
@@ -64,8 +64,9 @@ public:
*/
//@{
/// This method starts a task dialog in the task view
void showDialog(Gui::TaskView::TaskDialog* dlg);
Gui::TaskView::TaskDialog* activeDialog() const;
/// The dialog is relative to a specific document
void showDialog(Gui::TaskView::TaskDialog* dlg, App::Document* attachTo = nullptr);
Gui::TaskView::TaskDialog* activeDialog(App::Document* attachedTo = nullptr) const;
// void closeDialog();
//@}
@@ -81,28 +82,29 @@ public:
If a task dialog is open then it indicates whether this task dialog allows other commands to
modify the document while it is open. If no task dialog is open true is returned.
*/
bool isAllowedAlterDocument() const;
bool isAllowedAlterDocument(App::Document* attachedTo = nullptr) const;
/*!
If a task dialog is open then it indicates whether this task dialog allows other commands to
modify the 3d view while it is open. If no task dialog is open true is returned.
*/
bool isAllowedAlterView() const;
bool isAllowedAlterView(App::Document* attachedTo = nullptr) const;
/*!
If a task dialog is open then it indicates whether this task dialog allows other commands to
modify the selection while it is open. If no task dialog is open true is returned.
*/
bool isAllowedAlterSelection() const;
bool isAllowedAlterSelection(App::Document* attachedTo = nullptr) const;
public Q_SLOTS:
void accept();
void reject();
void closeDialog();
void accept(App::Document* attachedTo = nullptr);
void reject(App::Document* attachedTo = nullptr);
void closeDialog(App::Document* attachedTo = nullptr);
/// raises the task view panel
void showTaskView();
private Q_SLOTS:
private:
/// This get called by the TaskView when the Dialog is finished
void closedDialog();
void closedDialog(App::Document* attachedTo = nullptr);
private:
struct status
@@ -112,7 +114,7 @@ private:
std::stack<status> StatusStack;
Gui::TaskView::TaskDialog* ActiveDialog;
std::map<App::Document*, Gui::TaskView::TaskDialog*> ActiveDialogs;
int oldTabIndex;
private:
@@ -125,6 +127,9 @@ private:
void aboutToShowDialog(QDockWidget* widget);
void aboutToHideDialog(QDockWidget* widget);
// Returns attachTo if not nullptr, otherwise return the active document
static App::Document* docOrDefault(App::Document* attachedTo);
static ControlSingleton* _pcSingleton;
};
+2 -3
View File
@@ -194,14 +194,13 @@ void DemoMode::onAngleSliderValueChanged(int v)
void DemoMode::reorientCamera(SoCamera* cam, const SbRotation& rot)
{
// Find global coordinates of focal point.
SbVec3f direction;
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
SbVec3f focalpoint = cam->position.getValue() + cam->focalDistance.getValue() * direction;
SbVec3f focalpoint = activeView()->getViewer()->getFocalPoint();
// Set new orientation value by accumulating the new rotation.
cam->orientation = rot * cam->orientation.getValue();
// Reposition camera so we are still pointing at the same old focal point.
SbVec3f direction;
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
cam->position = focalpoint - cam->focalDistance.getValue() * direction;
}
+25 -18
View File
@@ -177,7 +177,7 @@ int DlgAddProperty::findLabelRow(const char* labelName, QFormLayout* layout)
{
for (int row = 0; row < layout->rowCount(); ++row) {
QLayoutItem* labelItem = layout->itemAt(row, QFormLayout::LabelRole);
if (labelItem == nullptr) {
if (!labelItem) {
continue;
}
@@ -204,7 +204,7 @@ void DlgAddProperty::removeExistingWidget(QFormLayout* formLayout, int labelRow)
void DlgAddProperty::setWidgetForLabel(const char* labelName, QWidget* widget, QLayout* layout)
{
auto formLayout = qobject_cast<QFormLayout*>(layout);
if (formLayout == nullptr) {
if (!formLayout) {
FC_ERR("Form layout not found");
return;
}
@@ -403,7 +403,7 @@ void DlgAddProperty::addEditor(PropertyItem* propertyItem)
else {
addNormalEditor(propertyItem);
}
if (editor == nullptr) {
if (!editor) {
return;
}
@@ -451,12 +451,12 @@ bool DlgAddProperty::isExcluded(const Base::Type& type) const
bool DlgAddProperty::isTypeWithEditor(PropertyItem* propertyItem) const
{
if (propertyItem == nullptr) {
if (!propertyItem) {
return false;
}
App::Property* prop = propertyItem->getFirstProperty();
if (prop == nullptr) {
if (!prop) {
return false;
}
@@ -689,7 +689,7 @@ void DlgAddProperty::showStatusMessage()
void DlgAddProperty::removeEditor()
{
if (editor == nullptr) {
if (!editor) {
return;
}
@@ -713,7 +713,7 @@ QVariant DlgAddProperty::getEditorData() const
{
if (isEnumPropertyItem()) {
PropertyItem* child = propertyItem->child(0);
if (child == nullptr) {
if (!child) {
return {};
}
return child->editorData(editor.get());
@@ -726,7 +726,7 @@ void DlgAddProperty::setEditorData(const QVariant& data)
{
if (isEnumPropertyItem()) {
PropertyItem* child = propertyItem->child(0);
if (child == nullptr) {
if (!child) {
return;
}
child->setEditorData(editor.get(), data);
@@ -741,7 +741,7 @@ void DlgAddProperty::setEditor(bool valueNeedsReset)
if (editor && !valueNeedsReset) {
QVariant data = getEditorData();
addEditor(propertyItem.get());
if (editor == nullptr) {
if (!editor) {
return;
}
setEditorData(data);
@@ -762,15 +762,15 @@ void DlgAddProperty::setEditor(bool valueNeedsReset)
void DlgAddProperty::setPropertyItem(App::Property* prop, bool supportsExpressions)
{
if (prop == nullptr) {
if (!prop) {
return;
}
if (propertyItem == nullptr) {
if (!propertyItem) {
propertyItem.reset(createPropertyItem(prop));
}
if (propertyItem == nullptr) {
if (!propertyItem) {
return;
}
@@ -816,7 +816,7 @@ bool DlgAddProperty::clearBoundProperty()
bool DlgAddProperty::clear(FieldChange fieldChange)
{
if (propertyItem == nullptr) {
if (!propertyItem) {
return true;
}
@@ -890,7 +890,7 @@ void DlgAddProperty::changeEvent(QEvent* e)
void DlgAddProperty::valueChangedEnum()
{
auto* propEnum = static_cast<App::PropertyEnumeration*>(propertyItem->getFirstProperty());
if (propEnum == nullptr || propertyItem->childCount() == 0) {
if (!propEnum || propertyItem->childCount() == 0) {
return;
}
@@ -920,7 +920,9 @@ void DlgAddProperty::valueChanged()
*/
void DlgAddProperty::openTransaction()
{
transactionID = App::GetApplication().setActiveTransaction("Add property");
transactionID = App::GetApplication().setActiveTransaction(
App::TransactionName {.name = "Add property", .temporary = false}
);
}
void DlgAddProperty::critical(const QString& title, const QString& text)
@@ -993,7 +995,12 @@ void DlgAddProperty::closeTransaction(TransactionOption option)
return;
}
App::GetApplication().closeActiveTransaction(static_cast<bool>(option), transactionID);
if (option == TransactionOption::Abort) {
App::GetApplication().abortTransaction(transactionID);
}
else {
App::GetApplication().commitTransaction(transactionID);
}
transactionID = 0;
}
@@ -1019,13 +1026,13 @@ void DlgAddProperty::addDocumentation()
std::string group = comboBoxGroup.currentText().toStdString();
std::string doc = ui->lineEditToolTip->text().toStdString();
if (propertyItem == nullptr) {
if (!propertyItem) {
// If there is no property item, we cannot add documentation.
return;
}
App::Property* prop = propertyItem->getFirstProperty();
if (prop == nullptr) {
if (!prop) {
return;
}
+5 -5
View File
@@ -697,7 +697,7 @@ static App::Document* getPreselectedDocument()
}
App::Document* doc = App::GetApplication().getDocument(lastDoc.c_str());
if (doc == nullptr) {
if (!doc) {
return App::GetApplication().getActiveDocument();
}
@@ -728,7 +728,7 @@ int DlgExpressionInput::getVarSetIndex(const App::Document* doc) const
void DlgExpressionInput::preselectVarSet()
{
const App::Document* doc = getPreselectedDocument();
if (doc == nullptr) {
if (!doc) {
FC_ERR("No active document found");
}
ui->comboBoxVarSet->setCurrentIndex(getVarSetIndex(doc));
@@ -763,7 +763,7 @@ static void addVarSetsVarSetComboBox(
auto* vp = freecad_cast<Gui::ViewProviderDocumentObject*>(
Gui::Application::Instance->getViewProvider(varSet)
);
if (vp == nullptr) {
if (!vp) {
FC_ERR("No ViewProvider found for VarSet: " << varSet->getNameInDocument());
continue;
}
@@ -889,13 +889,13 @@ void DlgExpressionInput::onVarSetSelected(int /*index*/)
}
App::Document* doc = App::GetApplication().getDocument(docName.toUtf8());
if (doc == nullptr) {
if (!doc) {
FC_ERR("Document not found: " << docName.toStdString());
return;
}
App::DocumentObject* varSet = doc->getObject(varSetName.toUtf8());
if (varSet == nullptr) {
if (!varSet) {
FC_ERR("Variable set not found: " << varSetName.toStdString());
return;
}
+1 -1
View File
@@ -71,7 +71,7 @@ public:
return "DockWindow";
}
/// Message handler
bool onMsg(const char*, const char**) override
bool onMsg(const char*) override
{
return false;
}
+78 -51
View File
@@ -90,6 +90,7 @@ struct DocumentP
bool _isClosing;
bool _isModified;
bool _isTransacting;
bool _isActive;
bool _changeViewTouchDocument;
bool _editWantsRestore;
bool _editWantsRestorePrevious;
@@ -101,6 +102,7 @@ struct DocumentP
ViewProviderDocumentObject* _editViewProviderParent;
std::string _editSubname;
std::string _editSubElement;
std::string _workbenchName; // Name of the workbench acting on this document
Base::Matrix4D _editingTransform;
View3DInventorViewer* _editingViewer;
std::set<const App::DocumentObject*> _editObjs;
@@ -284,6 +286,8 @@ struct DocumentP
{
_editingObject = sobj;
_editMode = ModNum;
_editViewProvider = svp; // Used to resolve start editing (find the document in edit from
// within the viewprovider)
_editViewProvider = svp->startEditing(ModNum);
if (!_editViewProvider) {
_editViewProviderParent = nullptr;
@@ -313,7 +317,7 @@ struct DocumentP
void setDocumentNameOfTaskDialog(App::Document* doc)
{
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(_pcDocument);
if (dlg) {
dlg->setDocumentName(doc->getName());
}
@@ -432,6 +436,7 @@ Document::Document(App::Document* pcDocument, Application* app)
d->_isClosing = false;
d->_isModified = false;
d->_isTransacting = false;
d->_isActive = false;
d->_pcAppWnd = app;
d->_pcDocument = pcDocument;
d->_editViewProvider = nullptr;
@@ -690,7 +695,6 @@ bool Document::trySetEdit(Gui::ViewProvider* p, int ModNum, const char* subname)
d->setEditingViewerIfPossible(view3d, ModNum);
d->signalEditMode();
App::AutoTransaction::setEnable(false);
return true;
}
@@ -717,7 +721,7 @@ void Document::resetEdit()
Gui::ViewProvider* vpToRestore = d->_editViewProviderPrevious;
bool shouldRestorePrevious = d->_editWantsRestorePrevious;
Application::Instance->setEditDocument(nullptr);
Application::Instance->unsetEditDocument(this);
if (vpIsNotNull && vpHasChanged && shouldRestorePrevious) {
setEdit(vpToRestore, modeToRestore);
@@ -755,18 +759,16 @@ void Document::_resetEdit()
// The logic below is not necessary anymore, because this method is
// changed into a private one, _resetEdit(). And the exposed
// resetEdit() above calls into Application->setEditDocument(0) which
// resetEdit() above calls into Application->unsetEditDocument() which
// will prevent recursive calling.
App::GetApplication().closeActiveTransaction();
App::GetApplication().commitTransaction(getDocument()->getBookedTransactionID());
}
d->_editViewProviderParent = nullptr;
d->_editingViewer = nullptr;
d->_editObjs.clear();
d->_editingObject = nullptr;
if (Application::Instance->editDocument() == this) {
Application::Instance->setEditDocument(nullptr);
}
Application::Instance->unsetEditDocument(this);
}
ViewProvider* Document::getInEdit(
@@ -799,6 +801,10 @@ ViewProvider* Document::getInEdit(
return nullptr;
}
ViewProvider* Document::getEditViewProvider() const
{
return d->_editViewProvider;
}
void Document::setInEdit(ViewProviderDocumentObject* parentVp, const char* subname)
{
@@ -1068,12 +1074,11 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj)
if (d->_editViewProvider == viewProvider || d->_editViewProviderParent == viewProvider) {
_resetEdit();
}
else if (Application::Instance->editDocument()) {
auto editDoc = Application::Instance->editDocument();
if (editDoc->d->_editViewProvider == viewProvider
|| editDoc->d->_editViewProviderParent == viewProvider) {
Application::Instance->setEditDocument(nullptr);
}
else {
Application::Instance->unsetEditDocumentIf([&viewProvider](Gui::Document* editdoc) {
return editdoc->d->_editViewProvider == viewProvider
|| editdoc->d->_editViewProviderParent == viewProvider;
});
}
handleChildren3D(viewProvider, true);
@@ -1096,15 +1101,13 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj)
void Document::beforeDelete()
{
auto editDoc = Application::Instance->editDocument();
if (editDoc) {
Application::Instance->unsetEditDocumentIf([this](Gui::Document* editDoc) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(editDoc->d->_editViewProvider);
auto vpp = freecad_cast<ViewProviderDocumentObject*>(editDoc->d->_editViewProviderParent);
if (editDoc == this || (vp && vp->getDocument() == this)
|| (vpp && vpp->getDocument() == this)) {
Application::Instance->setEditDocument(nullptr);
}
}
return editDoc == this || (vp && vp->getDocument() == this)
|| (vpp && vpp->getDocument() == this);
});
for (auto& v : d->_ViewProviderMap) {
v.second->beforeDelete();
}
@@ -1251,6 +1254,18 @@ void Document::slotSkipRecompute(const App::Document& doc, const std::vector<App
return;
}
App::DocumentObject* obj = nullptr;
if (Gui::Application::Instance->isInEdit(this)) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(getInEdit());
if (vp) {
obj = vp->getObject();
}
}
if (objs.size() > 1 || App::GetApplication().getActiveDocument() != &doc
|| !doc.testStatus(App::Document::AllowPartialRecompute)) {
return;
}
auto editDoc = Application::Instance->editDocument();
if (editDoc) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
@@ -1329,6 +1344,14 @@ bool Document::isModified() const
{
return d->_isModified;
}
void Document::setWorkbench(const std::string& name)
{
d->_workbenchName = name;
}
std::string Document::workbench() const
{
return d->_workbenchName;
}
bool Document::isAboutToClose() const
{
@@ -1399,6 +1422,17 @@ App::Document* Document::getDocument() const
{
return d->_pcDocument;
}
void Document::setIsActive(bool active)
{
d->_isActive = active;
if (d->_editViewProvider) {
d->_editViewProvider->setActive(active);
}
}
bool Document::isActive() const
{
return d->_isActive;
}
static bool checkCanonicalPath(const std::map<App::Document*, bool>& docs)
{
@@ -1568,7 +1602,7 @@ bool Document::save()
for (auto doc : docs) {
// Changed 'mustExecute' status may be triggered by saving external document
if (!dmap[doc] && doc->mustExecute()) {
App::AutoTransaction trans("Recompute");
App::AutoTransaction trans(doc, "Recompute");
Command::doCommand(
Command::Doc,
"App.getDocument(\"%s\").recompute()",
@@ -1711,7 +1745,7 @@ void Document::saveAll()
try {
// Changed 'mustExecute' status may be triggered by saving external document
if (!dmap[doc] && doc->mustExecute()) {
App::AutoTransaction trans("Recompute");
App::AutoTransaction trans(doc, "Recompute");
Command::doCommand(Command::Doc, "App.getDocument('%s').recompute()", doc->getName());
}
Command::doCommand(Command::Doc, "App.getDocument('%s').save()", doc->getName());
@@ -1898,11 +1932,9 @@ void Document::RestoreDocFile(Base::Reader& reader)
if (!Base::Tools::isNullOrEmpty(ppReturn)) {
saveCameraSettings(ppReturn);
try {
const char** pReturnIgnore = nullptr;
std::list<MDIView*> mdi = getMDIViews();
for (const auto& it : mdi) {
if (it->onHasMsg("SetCamera")) {
it->onMsg(cameraSettings.c_str(), pReturnIgnore);
for (const auto& it : getMDIViews()) {
if (auto* view3D = freecad_cast<View3DInventor*>(it)) {
view3D->setCamera(cameraSettings.c_str());
}
}
}
@@ -2023,12 +2055,10 @@ void Document::SaveDocFile(Base::Writer& writer) const
writer.decInd(); // indentation for 'ViewProviderData Count'
// save camera settings
std::list<MDIView*> mdi = getMDIViews();
for (const auto& it : mdi) {
if (it->onHasMsg("GetCamera")) {
const char* ppReturn = nullptr;
it->onMsg("GetCamera", &ppReturn);
if (saveCameraSettings(ppReturn)) {
for (const auto& it : getMDIViews()) {
if (auto* view3D = freecad_cast<View3DInventor*>(it)) {
const std::string& camera = view3D->getCamera();
if (saveCameraSettings(camera.c_str())) {
break;
}
}
@@ -2217,9 +2247,8 @@ MDIView* Document::createView(const Base::Type& typeId, CreateViewMode mode)
auto firstView = static_cast<View3DInventor*>(theViews.front());
shareWidget = qobject_cast<QOpenGLWidget*>(firstView->getViewer()->getGLWidget());
const char* ppReturn = nullptr;
firstView->onMsg("GetCamera", &ppReturn);
saveCameraSettings(ppReturn);
const std::string& camera = firstView->getCamera();
saveCameraSettings(camera.c_str());
}
auto view3D = new View3DInventor(this, getMainWindow(), shareWidget);
@@ -2265,8 +2294,7 @@ MDIView* Document::createView(const Base::Type& typeId, CreateViewMode mode)
view3D->resize(400, 300);
if (!cameraSettings.empty()) {
const char* ppReturn = nullptr;
view3D->onMsg(cameraSettings.c_str(), &ppReturn);
view3D->setCamera(cameraSettings.c_str());
}
// When cloning the view, don't add the view to the main window. The whole purpose of the
@@ -2284,7 +2312,7 @@ MDIView* Document::createView(const Base::Type& typeId, CreateViewMode mode)
const char* Document::getCameraSettings() const
{
return cameraSettings.size() > 10 ? cameraSettings.c_str() + 10 : cameraSettings.c_str();
return cameraSettings.c_str();
}
bool Document::saveCameraSettings(const char* settings) const
@@ -2314,7 +2342,7 @@ bool Document::saveCameraSettings(const char* settings) const
return false;
}
cameraSettings = std::string("SetCamera ") + settings;
cameraSettings = settings;
return true;
}
@@ -2482,8 +2510,8 @@ bool Document::canClose(bool checkModify, bool checkLink)
// If a task dialog is open that doesn't allow other commands to modify
// the document it must be closed by resetting the edit mode of the
// corresponding view provider.
if (!Gui::Control().isAllowedAlterDocument()) {
std::string name = Gui::Control().activeDialog()->getDocumentName();
if (!Gui::Control().isAllowedAlterDocument(getDocument())) {
std::string name = Gui::Control().activeDialog(getDocument())->getDocumentName();
if (name == this->getDocument()->getName()) {
// getInEdit() only checks if the currently active MDI view is
// a 3D view and that it is in edit mode. However, when closing a
@@ -2531,16 +2559,15 @@ std::list<MDIView*> Document::getMDIViewsOfType(const Base::Type& typeId) const
bool Document::sendMsgToViews(const char* pMsg)
{
std::list<Gui::BaseView*>::iterator it;
const char** pReturnIgnore = nullptr;
for (it = d->baseViews.begin(); it != d->baseViews.end(); ++it) {
if ((*it)->onMsg(pMsg, pReturnIgnore)) {
if ((*it)->onMsg(pMsg)) {
return true;
}
}
for (it = d->passiveViews.begin(); it != d->passiveViews.end(); ++it) {
if ((*it)->onMsg(pMsg, pReturnIgnore)) {
if ((*it)->onMsg(pMsg)) {
return true;
}
}
@@ -2548,12 +2575,12 @@ bool Document::sendMsgToViews(const char* pMsg)
return false;
}
bool Document::sendMsgToFirstView(const Base::Type& typeId, const char* pMsg, const char** ppReturn)
bool Document::sendMsgToFirstView(const Base::Type& typeId, const char* pMsg)
{
// first try the active view
Gui::MDIView* view = getActiveView();
if (view && view->isDerivedFrom(typeId)) {
if (view->onMsg(pMsg, ppReturn)) {
if (view->onMsg(pMsg)) {
return true;
}
}
@@ -2561,7 +2588,7 @@ bool Document::sendMsgToFirstView(const Base::Type& typeId, const char* pMsg, co
// now try the other views
std::list<Gui::MDIView*> views = getMDIViewsOfType(typeId);
for (const auto& it : views) {
if ((it != view) && it->onMsg(pMsg, ppReturn)) {
if ((it != view) && it->onMsg(pMsg)) {
return true;
}
}
@@ -2740,9 +2767,9 @@ Gui::MDIView* Document::getEditingViewOfViewProvider(Gui::ViewProvider* vp) cons
* operation default is the command name.
* @see CommitCommand(),AbortCommand()
*/
void Document::openCommand(const char* sName)
int Document::openCommand(const char* sName)
{
getDocument()->openTransaction(sName);
return getDocument()->openTransaction(App::TransactionName {.name = sName, .temporary = false});
}
void Document::commitCommand()
+13 -2
View File
@@ -191,12 +191,21 @@ public:
void setModified(bool);
bool isModified() const;
/// getter-setter for workbench name
void setWorkbench(const std::string& name);
std::string workbench() const;
/// Returns true if the document is about to be closed, false otherwise
bool isAboutToClose() const;
/// Getter for the App Document
App::Document* getDocument() const;
/// Notify the document when it becomes
/// the active document/stops being the active document
void setIsActive(bool active);
bool isActive() const;
/** @name methods for View handling */
//@{
/// Getter for the active view
@@ -218,7 +227,7 @@ public:
* first checked view is the current active view.
* If a view supports the message true is returned and false otherwise.
*/
bool sendMsgToFirstView(const Base::Type& typeId, const char* pMsg, const char** ppReturn);
bool sendMsgToFirstView(const Base::Type& typeId, const char* pMsg);
/// Attach a view (get called by the MDIView constructor)
void attachView(Gui::BaseView* pcView, bool bPassiv = false);
/// Detach a view (get called by the MDIView destructor)
@@ -287,6 +296,8 @@ public:
int* mode = nullptr,
std::string* subElement = nullptr
) const;
ViewProvider* getEditViewProvider() const; // Returns the _editViewProvider even if it is not
// in edit at the moment
/// set the in edit ViewProvider subname reference
void setInEdit(ViewProviderDocumentObject* parentVp, const char* subname);
/** Add or remove view provider from scene graphs of all views
@@ -300,7 +311,7 @@ public:
/** @name methods for the UNDO REDO handling */
//@{
/// Open a new Undo transaction on the document
void openCommand(const char* sName = nullptr);
int openCommand(const char* sName = nullptr);
/// Commit the Undo transaction on the document
void commitCommand();
/// Abort the Undo transaction on the document
+25
View File
@@ -187,6 +187,31 @@ class Document(Persistence):
obj : Gui.ViewProvider
"""
...
def openCommand(self, name: str) -> int:
"""
openCommand(name) -> int
Opens a named transaction for the document and returns it's
id or 0 on failure
"""
...
def commitCommand(self) -> None:
"""
commitCommand() -> None
Commits the current transaction of the document
"""
...
def abortCommand(self) -> None:
"""
abortCommand() -> None
Aborts the current transaction of the document
"""
...
ActiveObject: Any = ...
"""The active object of the document."""
+28
View File
@@ -577,6 +577,34 @@ Py::Long DocumentPy::getEditMode() const
return Py::Long(mode);
}
PyObject* DocumentPy::openCommand(PyObject* arg, PyObject* kwd)
{
const char* name = nullptr;
if (!PyArg_ParseTuple(arg, "s", &name)) {
throw Py::Exception();
}
int tid = getDocumentPtr()->openCommand(name);
return Py::new_reference_to(Py::Long(tid));
}
PyObject* DocumentPy::commitCommand(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
getDocumentPtr()->commitCommand();
Py_Return;
}
PyObject* DocumentPy::abortCommand(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
getDocumentPtr()->abortCommand();
Py_Return;
}
Py::Boolean DocumentPy::getTransacting() const
{
return {getDocumentPtr()->isPerformingTransaction()};
+56 -50
View File
@@ -29,9 +29,11 @@
#include <Inventor/nodes/SoSwitch.h>
#include <QEvent>
#include <QFontMetrics>
#include <QKeyEvent>
#include <QPixmap>
#include <QLabel>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QString>
@@ -41,6 +43,8 @@
#include <Gui/View3DInventorViewer.h>
#include "EditableDatumLabel.h"
#include "Base/Console.h"
#include "Gui/QuantitySpinBox.h"
using namespace Gui;
@@ -175,6 +179,8 @@ void EditableDatumLabel::startEdit(double val, QObject* eventFilteringObj, bool
spinBox->setAutoNormalize(false);
spinBox->setKeyboardTracking(true);
spinBox->installEventFilter(this);
spinBox->setAutoAdjustWidth(true);
spinBox->setMaxExpectedDigits(16);
if (eventFilteringObj) {
spinBox->installEventFilter(eventFilteringObj);
@@ -185,10 +191,7 @@ void EditableDatumLabel::startEdit(double val, QObject* eventFilteringObj, bool
}
spinBox->show();
setSpinboxValue(val);
// Note: adjustSize apparently uses the Min/Max values to set the size. So if we don't set them
// to INT_MAX, the spinbox are much too big.
spinBox->adjustSize();
updateGeometry();
setFocusToSpinbox();
const auto validateAndFinish = [this]() {
@@ -217,6 +220,9 @@ void EditableDatumLabel::startEdit(double val, QObject* eventFilteringObj, bool
};
connect(spinBox, qOverload<double>(&QuantitySpinBox::valueChanged), this, validateAndFinish);
if (auto* edit = spinBox->findChild<QLineEdit*>()) {
connect(edit, &QLineEdit::textChanged, this, &EditableDatumLabel::updateGeometry);
}
}
bool EditableDatumLabel::eventFilter(QObject* watched, QEvent* event)
@@ -364,8 +370,9 @@ void EditableDatumLabel::positionSpinbox()
// Update lock icon position inside the spinbox if it exists and is visible
if (lockIconLabel && lockIconLabel->isVisible()) {
int iconSize = 14;
int padding = 4;
const QFontMetrics fm(spinBox->fontMetrics());
int iconSize = fm.height();
int padding = spinBox->getMargin();
QSize spinboxSize = spinBox->size();
lockIconLabel->setGeometry(
spinboxSize.width() - iconSize - padding,
@@ -424,6 +431,14 @@ void EditableDatumLabel::setPlacement(const Base::Placement& plc)
label->norm.setValue(SbVec3f(float(RN.x), float(RN.y), float(RN.z)));
}
void EditableDatumLabel::updateGeometry()
{
if (!spinBox) {
return;
}
spinBox->adjustSize();
}
// NOLINTNEXTLINE
void EditableDatumLabel::setColor(SbColor color)
{
@@ -510,56 +525,47 @@ void EditableDatumLabel::setSpinboxVisibleToMouse(bool val)
void EditableDatumLabel::setLockedAppearance(bool locked)
{
if (!locked) {
this->hasFinishedEditing = false;
}
if (!spinBox) {
return;
}
const QFontMetrics fm(spinBox->fontMetrics());
int iconSize = fm.height();
int padding = spinBox->getMargin();
if (locked) {
if (spinBox) {
spinBox->addIconSpace(true);
spinBox->adjustSize();
// create lock icon label it it doesn't exist, if it does - show it
if (!lockIconLabel) {
lockIconLabel = new QLabel(spinBox);
lockIconLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
lockIconLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
// create lock icon label it it doesn't exist, if it does - show it
if (!lockIconLabel) {
lockIconLabel = new QLabel(spinBox);
lockIconLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
lockIconLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
// load icon and scale it to fit in spinbox
QPixmap lockIcon = Gui::BitmapFactory().pixmap("Constraint_Lock");
QPixmap scaledIcon
= lockIcon.scaled(iconSize, iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
lockIconLabel->setPixmap(scaledIcon);
// load icon and scale it to fit in spinbox
QPixmap lockIcon = Gui::BitmapFactory().pixmap("Constraint_Lock");
QPixmap scaledIcon
= lockIcon.scaled(14, 14, Qt::KeepAspectRatio, Qt::SmoothTransformation);
lockIconLabel->setPixmap(scaledIcon);
// position lock icon inside the spinbox
int iconSize = 14;
int padding = 4;
QSize spinboxSize = spinBox->size();
lockIconLabel->setGeometry(
spinboxSize.width() - iconSize - padding,
(spinboxSize.height() - iconSize) / 2,
iconSize,
iconSize
);
// style spinbox and add padding for lock
QString styleSheet = QString::fromLatin1(
"QSpinBox { "
"padding-right: %1px; "
"}"
)
.arg(iconSize + padding + 2);
spinBox->setStyleSheet(styleSheet);
}
lockIconLabel->show();
// position lock icon inside the spinbox
QSize spinboxSize = spinBox->size();
lockIconLabel->setGeometry(
spinboxSize.width() - iconSize - padding,
(spinboxSize.height() - iconSize) / 2,
iconSize,
iconSize
);
}
lockIconLabel->show();
}
else {
this->hasFinishedEditing = false;
// if spinbox exists, reset its appearance
if (spinBox) {
spinBox->setStyleSheet(QString());
// hide lock icon if it exists for later reuse
if (lockIconLabel) {
lockIconLabel->hide();
}
// hide lock icon if it exists for later reuse
if (lockIconLabel) {
lockIconLabel->hide();
spinBox->addIconSpace(false);
spinBox->adjustSize();
}
}
}
+1
View File
@@ -92,6 +92,7 @@ public:
void setLockedAppearance(bool locked); ///< Sets visual appearance to indicate locked state
///< (finished editing)
void resetLockedState(); ///< Resets both hasFinishedEditing flag and locked appearance
void updateGeometry();
Function getFunction();
+4 -4
View File
@@ -233,7 +233,7 @@ void EditorView::checkTimestamp()
/**
* Runs the action specified by \a pMsg.
*/
bool EditorView::onMsg(const char* pMsg, const char** /*ppReturn*/)
bool EditorView::onMsg(const char* pMsg)
{
// don't allow any actions if the editor is being closed
if (d->aboutToClose) {
@@ -663,7 +663,7 @@ PythonEditorView::~PythonEditorView()
/**
* Runs the action specified by \a pMsg.
*/
bool PythonEditorView::onMsg(const char* pMsg, const char** ppReturn)
bool PythonEditorView::onMsg(const char* pMsg)
{
if (strcmp(pMsg, "Run") == 0) {
executeScript();
@@ -677,7 +677,7 @@ bool PythonEditorView::onMsg(const char* pMsg, const char** ppReturn)
toggleBreakpoint();
return true;
}
return EditorView::onMsg(pMsg, ppReturn);
return EditorView::onMsg(pMsg);
}
/**
@@ -705,7 +705,7 @@ void PythonEditorView::executeScript()
{
// always save the macro when it is modified
if (EditorView::onHasMsg("Save")) {
EditorView::onMsg("Save", nullptr);
EditorView::onMsg("Save");
}
try {
getMainWindow()->setCursor(Qt::WaitCursor);
+2 -2
View File
@@ -76,7 +76,7 @@ public:
void onUpdate() override
{}
bool onMsg(const char* pMsg, const char** ppReturn) override;
bool onMsg(const char* pMsg) override;
bool onHasMsg(const char* pMsg) const override;
bool canClose() override;
@@ -135,7 +135,7 @@ public:
PythonEditorView(PythonEditor* editor, QWidget* parent);
~PythonEditorView() override;
bool onMsg(const char* pMsg, const char** ppReturn) override;
bool onMsg(const char* pMsg) override;
bool onHasMsg(const char* pMsg) const override;
public Q_SLOTS:
+11 -11
View File
@@ -75,11 +75,11 @@ void Gui::ExpressionBinding::setExpression(std::shared_ptr<Expression> expr)
lastExpression = getExpression();
bool transaction = !App::GetApplication().getActiveTransaction();
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
if (transaction) {
std::ostringstream ss;
ss << (expr ? "Set" : "Discard") << " expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
docObj->getDocument()->openTransaction(ss.str().c_str());
}
docObj->ExpressionEngine.setValue(path, expr);
@@ -89,7 +89,7 @@ void Gui::ExpressionBinding::setExpression(std::shared_ptr<Expression> expr)
}
if (transaction) {
App::GetApplication().closeActiveTransaction();
docObj->getDocument()->commitTransaction();
}
}
@@ -97,7 +97,7 @@ void ExpressionBinding::bind(const App::ObjectIdentifier& _path)
{
const Property* prop = _path.getProperty();
Q_ASSERT(prop != nullptr);
Q_ASSERT(prop);
path = prop->canonicalPath(_path);
@@ -133,7 +133,7 @@ std::shared_ptr<App::Expression> ExpressionBinding::getExpression() const
{
DocumentObject* docObj = path.getDocumentObject();
Q_ASSERT(isBound() && docObj != nullptr);
Q_ASSERT(isBound() && docObj);
return docObj->getExpression(path).expression;
}
@@ -214,11 +214,11 @@ bool ExpressionBinding::apply(const std::string& propName)
throw Base::RuntimeError("Document object not found.");
}
bool transaction = !App::GetApplication().getActiveTransaction();
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
if (transaction) {
std::ostringstream ss;
ss << "Set expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
docObj->getDocument()->openTransaction(ss.str().c_str());
}
Gui::Command::doCommand(
Gui::Command::Doc,
@@ -229,7 +229,7 @@ bool ExpressionBinding::apply(const std::string& propName)
getEscapedExpressionString().c_str()
);
if (transaction) {
App::GetApplication().closeActiveTransaction();
docObj->getDocument()->commitTransaction();
}
return true;
}
@@ -242,11 +242,11 @@ bool ExpressionBinding::apply(const std::string& propName)
}
if (lastExpression) {
bool transaction = !App::GetApplication().getActiveTransaction();
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
if (transaction) {
std::ostringstream ss;
ss << "Discard expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
docObj->getDocument()->openTransaction(ss.str().c_str());
}
Gui::Command::doCommand(
Gui::Command::Doc,
@@ -256,7 +256,7 @@ bool ExpressionBinding::apply(const std::string& propName)
path.toEscapedString().c_str()
);
if (transaction) {
App::GetApplication().closeActiveTransaction();
docObj->getDocument()->commitTransaction();
}
}
}
+1 -1
View File
@@ -471,7 +471,7 @@ QByteArray GraphvizView::exportGraph(const QString& format)
return dotProc.readAll();
}
bool GraphvizView::onMsg(const char* pMsg, const char**)
bool GraphvizView::onMsg(const char* pMsg)
{
if (strcmp("Save", pMsg) == 0 || strcmp("SaveAs", pMsg) == 0) {
QList<QPair<QString, QString>> formatMap;
+1 -1
View File
@@ -48,7 +48,7 @@ public:
QByteArray exportGraph(const QString& filter);
/// Message handler
bool onMsg(const char* pMsg, const char** ppReturn) override;
bool onMsg(const char* pMsg) override;
/// Message handler test
bool onHasMsg(const char* pMsg) const override;
/** @name Printing */
+1 -2
View File
@@ -311,9 +311,8 @@ void ImageView::print(QPrinter* printer)
painter.drawPixmap(0, 0, pixmap);
}
bool ImageView::onMsg(const char* pMsg, const char** ppReturn)
bool ImageView::onMsg(const char* pMsg)
{
Q_UNUSED(ppReturn)
if (strcmp("ViewFit", pMsg) == 0) {
fitToWindow(true);
return true;
+1 -1
View File
@@ -48,7 +48,7 @@ public:
}
/// Message handler
bool onMsg(const char* pMsg, const char** ppReturn) override;
bool onMsg(const char* pMsg) override;
/// Message handler test
bool onHasMsg(const char* pMsg) const override;
+2 -2
View File
@@ -315,7 +315,7 @@
<location filename="../Dialogs/DlgCustomizeSpNavSettings.ui" line="318"/>
<location filename="../Dialogs/DlgCustomizeSpNavSettings.ui" line="342"/>
<source>Reverse</source>
<translation>Omvendt</translation>
<translation>Modsat</translation>
</message>
</context>
<context>
@@ -9279,7 +9279,7 @@ i den aktuelle kopi vil gå tabt.
<message>
<location filename="../CommandDoc.cpp" line="434"/>
<source>No selection</source>
<translation>Ingen valg</translation>
<translation>Ingen valgt</translation>
</message>
<message>
<location filename="../CommandDoc.cpp" line="435"/>
+1 -1
View File
@@ -6387,7 +6387,7 @@ sur Alt + bouton droit de la souris OU les touches Pg.Suiv/Pg.Préc du clavier</
<message>
<location filename="../Widgets.cpp" line="409"/>
<source>Press modifier keys</source>
<translation>Appuyez sur les touches de modification</translation>
<translation>Appuyer sur les touches de modificateur</translation>
</message>
</context>
<context>
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -12161,7 +12161,7 @@ the region are non-opaque.</source>
<message>
<location filename="../PreferencePages/DlgSettingsUI.cpp" line="124"/>
<source>No style sheet</source>
<translation type="unfinished">No style sheet</translation>
<translation>Немає таблиці стилів</translation>
</message>
</context>
<context>
@@ -14649,7 +14649,7 @@ This makes the docked panel stay transparent at all times.</translation>
<message>
<location filename="../Dialogs/DlgVersionMigrator.cpp" line="140"/>
<source>Welcome to %1 %2.%3</source>
<translation type="unfinished">Welcome to %1 %2.%3</translation>
<translation>Ласкаво просимо до %1 %2.%3</translation>
</message>
<message>
<location filename="../Dialogs/DlgVersionMigrator.cpp" line="143"/>
+1 -2
View File
@@ -202,10 +202,9 @@ void MDIView::viewAll()
{}
/// receive a message
bool MDIView::onMsg(const char* pMsg, const char** ppReturn)
bool MDIView::onMsg(const char* pMsg)
{
Q_UNUSED(pMsg);
Q_UNUSED(ppReturn);
return false;
}
+1 -1
View File
@@ -81,7 +81,7 @@ public:
void setWindowTitle(const QString& title);
/// Message handler
bool onMsg(const char* pMsg, const char** ppReturn) override;
bool onMsg(const char* pMsg) override;
/// Message handler test
bool onHasMsg(const char* pMsg) const override;
/// overwrite when checking on close state
+1 -2
View File
@@ -179,7 +179,6 @@ Py::Object MDIViewPy::redoActions(const Py::Tuple& args)
Py::Object MDIViewPy::sendMessage(const Py::Tuple& args)
{
const char** ppReturn = nullptr;
char* psMsgStr;
if (!PyArg_ParseTuple(args.ptr(), "s;Message string needed (string)", &psMsgStr)) {
throw Py::Exception();
@@ -188,7 +187,7 @@ Py::Object MDIViewPy::sendMessage(const Py::Tuple& args)
try {
bool ok = false;
if (_view) {
ok = _view->onMsg(psMsgStr, ppReturn);
ok = _view->onMsg(psMsgStr);
}
return Py::Boolean(ok);
}
+3 -3
View File
@@ -212,16 +212,16 @@ PyObject* MDIViewPyWrap::getPyObject()
return MDIView::getPyObject();
}
bool MDIViewPyWrap::onMsg(const char* pMsg, const char** ppReturn)
bool MDIViewPyWrap::onMsg(const char* pMsg)
{
try {
if (ptr->onMsg(pMsg)) {
return true;
}
return MDIView::onMsg(pMsg, ppReturn);
return MDIView::onMsg(pMsg);
}
catch (const std::exception&) {
return MDIView::onMsg(pMsg, ppReturn);
return MDIView::onMsg(pMsg);
}
catch (Py::Exception&) {
Base::PyGILStateLocker lock;
+1 -1
View File
@@ -55,7 +55,7 @@ public:
~MDIViewPyWrap() override;
/// Message handler
bool onMsg(const char* pMsg, const char** ppReturn) override;
bool onMsg(const char* pMsg) override;
/// Message handler test
bool onHasMsg(const char* pMsg) const override;
/// overwrite when checking on close state
+4 -4
View File
@@ -251,7 +251,7 @@ public:
void setUserSchema(int userSchema)
{
App::Document* doc = App::GetApplication().getActiveDocument();
if (doc != nullptr) {
if (doc) {
if (doc->UnitSystem.getValue() != userSchema) {
doc->UnitSystem.setValue(userSchema);
}
@@ -275,7 +275,7 @@ private:
bool ignore = hGrpu->GetBool("IgnoreProjectSchema", false);
App::Document* doc = App::GetApplication().getActiveDocument();
int userSchema = getWindowParameter()->GetInt("UserSchema", 0);
if (doc != nullptr && !ignore) {
if (doc && !ignore) {
userSchema = doc->UnitSystem.getValue();
}
auto actions = menu()->actions();
@@ -937,7 +937,7 @@ int MainWindow::confirmSave(App::Document* doc, QWidget* parent, bool addCheckbo
const QList<QWidget*> listOfMDIs = this->windows();
for (QWidget* widget : listOfMDIs) {
auto mdiView = qobject_cast<MDIView*>(widget);
if (mdiView != nullptr && mdiView->getAppDocument() == doc) {
if (mdiView && mdiView->getAppDocument() == doc) {
this->setActiveWindow(mdiView);
}
}
@@ -974,7 +974,7 @@ bool MainWindow::closeAllDocuments(bool close)
// moves the active document to the front
MDIView* activeView = this->activeWindow();
App::Document* activeDoc = (activeView ? activeView->getAppDocument() : nullptr);
if (activeDoc != nullptr) {
if (activeDoc) {
for (auto it = ++docs.begin(); it != docs.end(); it++) {
if (*it == activeDoc) {
docs.erase(it);
+1 -1
View File
@@ -1220,7 +1220,7 @@ bool NaviCubeImplementation::mouseReleased(short x, short y)
// If the previous flat button animation is still active then apply the rotation to the
// previous target orientation, otherwise apply the rotation to the current camera
// orientation
if (m_flatButtonAnimation != nullptr
if (m_flatButtonAnimation
&& m_flatButtonAnimation->state() == QAbstractAnimation::Running) {
m_flatButtonTargetOrientation = rotation * m_flatButtonTargetOrientation;
}
@@ -710,7 +710,7 @@ public:
: my_base(ctx)
{
auto& ns = this->outermost_context().ns;
ns.setRotationCenter(ns.getFocalPoint());
ns.setRotationCenter(ns.viewer->getFocalPoint());
ns.setViewingMode(NavigationStyle::DRAGGING);
this->base_pos
= static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getPosition();
@@ -589,7 +589,7 @@ SbBool MayaGestureNavigationStyle::processSoEvent(const SoEvent* const ev)
if (comboAfter & BUTTON1DOWN || comboAfter & BUTTON2DOWN) {
// don't leave navigation till all buttons have been released
if (comboAfter & BUTTON1DOWN && comboAfter & BUTTON2DOWN) {
setRotationCenter(getFocalPoint());
setRotationCenter(viewer->getFocalPoint());
}
else {
saveCursorPosition(ev);
+1 -1
View File
@@ -96,7 +96,7 @@ void NavigationAnimator::stop()
*/
bool NavigationAnimator::isAnimating() const
{
if (activeAnimation != nullptr) {
if (activeAnimation) {
return activeAnimation->state() == QAbstractAnimation::State::Running;
}
+8 -23
View File
@@ -504,7 +504,7 @@ void NavigationStyle::lookAtPoint(const SbVec2s screenpos)
void NavigationStyle::lookAtPoint(const SbVec3f& position)
{
this->rotationCenterFound = false;
translateCamera(position - getFocalPoint());
translateCamera(position - viewer->getFocalPoint());
}
SoCamera* NavigationStyle::getCamera() const
@@ -524,7 +524,7 @@ std::shared_ptr<NavigationAnimation> NavigationStyle::setCameraOrientation(
animator->stop();
const SbVec3f focalPoint = getFocalPoint();
const SbVec3f focalPoint = viewer->getFocalPoint();
SbVec3f translation(0, 0, 0);
if (moveToCenter) {
@@ -618,6 +618,7 @@ void NavigationStyle::boxZoom(const SbBox2s& box)
doScale(cam, scaleFactor);
}
void NavigationStyle::scale(float factor)
{
SoCamera* cam = viewer->getSoRenderManager()->getCamera();
@@ -626,9 +627,7 @@ void NavigationStyle::scale(float factor)
}
// Find the current center of the screen
SbVec3f direction;
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
SbVec3f initCenter = cam->position.getValue() + cam->focalDistance.getValue() * direction;
SbVec3f initCenter = viewer->getFocalPoint();
// Move the camera to the origin for scaling
cam->position = cam->position.getValue() - initCenter;
@@ -702,7 +701,7 @@ void NavigationStyle::findBoundingSphere()
*/
void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotation)
{
reorientCamera(camera, rotation, getFocalPoint());
reorientCamera(camera, rotation, viewer->getFocalPoint());
}
/** Rotate the camera by the given amount, then reposition it so the rotation center stays in the
@@ -751,7 +750,7 @@ void NavigationStyle::reorientCamera(
#else
constexpr float orthographicFocalDistance = 1;
#endif
camera->position = getFocalPoint() - orthographicFocalDistance * direction;
camera->position = viewer->getFocalPoint() - orthographicFocalDistance * direction;
camera->focalDistance = orthographicFocalDistance;
}
@@ -1004,7 +1003,7 @@ void NavigationStyle::doZoom(SoCamera* camera, float logfactor, const SbVec2f& p
// Change the position of the rotation center indicator after zooming at cursor
// Rotation mode is WindowCenter
if (!rotationCenterMode) {
viewer->changeRotationCenterPosition(getFocalPoint());
viewer->changeRotationCenterPosition(viewer->getFocalPoint());
#if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403)
findBoundingSphere();
@@ -1083,20 +1082,6 @@ void NavigationStyle::setRotationCenter(const SbVec3f& cnt)
}
}
SbVec3f NavigationStyle::getFocalPoint() const
{
SoCamera* cam = viewer->getSoRenderManager()->getCamera();
if (!cam) {
return {0, 0, 0};
}
// Find global coordinates of focal point.
SbVec3f direction;
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
SbVec3f focal = cam->position.getValue() + cam->focalDistance.getValue() * direction;
return focal;
}
/** Uses the sphere sheet projector to map the mouse position onto
* a 3D point and find a rotation from this and the last calculated point.
*/
@@ -1296,7 +1281,7 @@ void NavigationStyle::saveCursorPosition(const SoEvent* const ev)
// mode is WindowCenter
if (!this->rotationCenterMode) {
setRotationCenter(getFocalPoint());
setRotationCenter(viewer->getFocalPoint());
}
// Option to get point on model (slow) or always on focal plane (fast)
-1
View File
@@ -166,7 +166,6 @@ public:
void setRotationCenterMode(RotationCenterModes);
RotationCenterModes getRotationCenterMode() const;
void setRotationCenter(const SbVec3f& cnt);
SbVec3f getFocalPoint() const;
SoCamera* getCamera() const;
std::shared_ptr<NavigationAnimation> setCameraOrientation(
+3 -3
View File
@@ -1244,16 +1244,16 @@ bool OverlayTabWidget::checkAutoHide() const
}
}
bool activeDocInEdit = Application::Instance->isInEdit(Application::Instance->activeDocument());
if (autoMode == AutoMode::EditShow) {
return !Application::Instance->editDocument()
&& (!Control().taskPanel() || Control().taskPanel()->isEmpty(false));
return !activeDocInEdit && (!Control().taskPanel() || Control().taskPanel()->isEmpty(false));
}
if (autoMode == AutoMode::TaskShow) {
return (!Control().taskPanel() || Control().taskPanel()->isEmpty());
}
if (autoMode == AutoMode::EditHide && Application::Instance->editDocument()) {
if (autoMode == AutoMode::EditHide && activeDocInEdit) {
return true;
}
@@ -351,7 +351,7 @@ void DlgSettingsLightSources::resetSettingsToDefaults()
void DlgSettingsLightSources::zoomIn() const
{
if (camera == nullptr) {
if (!camera) {
return;
}
@@ -360,7 +360,7 @@ void DlgSettingsLightSources::zoomIn() const
void DlgSettingsLightSources::zoomOut() const
{
if (camera == nullptr) {
if (!camera) {
return;
}
+2 -2
View File
@@ -406,7 +406,7 @@ public:
const auto PyW_uniqueName = QString::number(reinterpret_cast<quintptr>(pyobj));
auto PyW_invalidator = findChild<QObject*>(PyW_uniqueName, Qt::FindDirectChildrenOnly);
if (PyW_invalidator == nullptr) {
if (!PyW_invalidator) {
PyW_invalidator = new QObject(this);
PyW_invalidator->setObjectName(PyW_uniqueName);
@@ -420,7 +420,7 @@ public:
auto destroyedFun = [pyobj]() {
Base::PyGILStateLocker lock;
if (auto sbkPtr = reinterpret_cast<SbkObject*>(pyobj); sbkPtr != nullptr) {
if (auto sbkPtr = reinterpret_cast<SbkObject*>(pyobj); sbkPtr) {
Shiboken::Object::setValidCpp(sbkPtr, false);
}
else {
@@ -93,10 +93,5 @@ ActionGroup *ActionPanel::createGroup(const QPixmap &icon, const QString &title,
return group;
}
QSize ActionPanel::minimumSizeHint() const
{
return {200,150};
}
} // namespace QSint
-6
View File
@@ -89,12 +89,6 @@ public:
*/
void setScheme(ActionPanelScheme *scheme);
/**
* @brief Returns the recommended minimum size for the panel.
* @return The minimum size hint.
*/
QSize minimumSizeHint() const override;
protected:
/** @brief The color scheme used by the panel. */
ActionPanelScheme *myScheme;
+99 -26
View File
@@ -66,6 +66,9 @@ public:
, pendingEmit(false)
, normalize(true)
, checkRangeInExpression(false)
, adjustableWidth(false)
, maxExpectedDigits(4)
, addIconSpace(false)
, unitValue(0)
, maximum(std::numeric_limits<double>::max())
, minimum(-std::numeric_limits<double>::max())
@@ -306,6 +309,9 @@ public:
bool pendingEmit;
bool normalize;
bool checkRangeInExpression;
bool adjustableWidth;
int maxExpectedDigits;
bool addIconSpace;
QString validStr;
Base::Quantity quantity;
Base::Quantity cached;
@@ -341,6 +347,9 @@ void QuantitySpinBox::bind(const App::ObjectIdentifier& _path)
void QuantitySpinBox::showIcon()
{
addIconSpace(true);
adjustSize();
iconLabel->show();
}
@@ -444,6 +453,19 @@ void QuantitySpinBox::resizeEvent(QResizeEvent* event)
{
QAbstractSpinBox::resizeEvent(event);
resizeWidget();
moveCursor();
}
void QuantitySpinBox::moveCursor()
{
Q_D(QuantitySpinBox);
QLineEdit* edit = lineEdit();
int cursor = edit->cursorPosition();
const int maxCursor = qMax(0, edit->displayText().size() - 1 /*space*/ - d->unitStr.size());
if (cursor > maxCursor) {
edit->setCursorPosition(maxCursor);
}
}
void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent* event)
@@ -467,6 +489,9 @@ void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent* event)
if (isEnter) {
returnPressed();
}
else {
moveCursor();
}
}
void Gui::QuantitySpinBox::paintEvent(QPaintEvent*)
@@ -504,7 +529,7 @@ void QuantitySpinBox::updateEdit(const QString& text)
edit->setSelection(0, cursor);
}
else {
edit->setCursorPosition(empty ? 0 : cursor);
moveCursor();
}
}
@@ -601,6 +626,42 @@ void QuantitySpinBox::setAutoNormalize(bool normalize)
d->normalize = normalize;
}
bool QuantitySpinBox::autoAdjustWidth() const
{
Q_D(const QuantitySpinBox);
return d->adjustableWidth;
}
void QuantitySpinBox::setAutoAdjustWidth(bool adjust)
{
Q_D(QuantitySpinBox);
d->adjustableWidth = adjust;
}
bool QuantitySpinBox::isIconSpaceAdded() const
{
Q_D(const QuantitySpinBox);
return d->addIconSpace;
}
void QuantitySpinBox::addIconSpace(bool addIconSpace)
{
Q_D(QuantitySpinBox);
d->addIconSpace = addIconSpace;
}
int QuantitySpinBox::getMaxExpectedDigits()
{
Q_D(const QuantitySpinBox);
return d->maxExpectedDigits;
}
void QuantitySpinBox::setMaxExpectedDigits(int digits)
{
Q_D(QuantitySpinBox);
d->maxExpectedDigits = digits;
}
bool QuantitySpinBox::hasValidInput() const
{
Q_D(const QuantitySpinBox);
@@ -611,7 +672,6 @@ bool QuantitySpinBox::hasValidInput() const
void QuantitySpinBox::userInput(const QString& text)
{
Q_D(QuantitySpinBox);
d->pendingEmit = true;
QString tmp = text;
@@ -911,44 +971,57 @@ QSize QuantitySpinBox::sizeForText(const QString& txt) const
QSize QuantitySpinBox::sizeHint() const
{
return sizeHintCalculator(lineEdit()->sizeHint().height());
Q_D(const QuantitySpinBox);
auto le = lineEdit();
if (le && d->adjustableWidth) {
// limit number of typed characters to keep unit visible
le->setMaxLength(getMaxStrLength(d->maxExpectedDigits));
}
return sizeHintForDigits(d->maxExpectedDigits);
}
QSize QuantitySpinBox::minimumSizeHint() const
{
return sizeHintCalculator(lineEdit()->minimumSizeHint().height());
}
QSize QuantitySpinBox::sizeHintCalculator(int h) const
QSize QuantitySpinBox::sizeHintForDigits(int digits) const
{
Q_D(const QuantitySpinBox);
ensurePolished();
const int maxLen = getMaxStrLength(digits);
int length = maxLen;
if (d->adjustableWidth) {
int currenLen = qMax(lineEdit()->text().length(), 4);
length = currenLen < maxLen ? currenLen : maxLen;
}
QString longestString = QStringLiteral("8").repeated(length);
const QFontMetrics fm(fontMetrics());
int w = 0;
constexpr int maxStrLen = 9;
QString s;
QString fixedContent = QLatin1String(" ");
Base::Quantity q(d->quantity);
q.setValue(d->maximum);
s = textFromValue(q);
s.truncate(maxStrLen);
s += fixedContent;
w = qMax(w, QtTools::horizontalAdvance(fm, s));
w += 2; // cursor blinking space
w += iconHeight;
int w = qMax(0, QtTools::horizontalAdvance(fm, longestString));
w += 4; // cursor blinking space
if (d->addIconSpace) {
w += iconHeight;
}
QStyleOptionSpinBox opt;
initStyleOption(&opt);
QSize hint(w, h);
QSize hint(w, lineEdit()->sizeHint().height());
QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this);
return size;
}
int QuantitySpinBox::getMaxStrLength(int digits) const
{
// Calculates the length of the longest string allowed
Q_D(const QuantitySpinBox);
ensurePolished();
QString unit = QString::fromStdString(d->unit.getString());
int decimals = App::GetApplication()
.GetUserParameter()
.GetGroup("BaseApp/Preferences/Units")
->GetInt("Decimals", 2);
return digits + 1 /*separator*/ + decimals + 1 /*space*/ + unit.length();
}
void QuantitySpinBox::showEvent(QShowEvent* event)
{
Q_D(QuantitySpinBox);

Some files were not shown because too many files have changed in this diff Show More