Skip to content

PartDesign: add interactive gizmos for box, cylinder and sphere operations#23700

Merged
chennes merged 2 commits intoFreeCAD:mainfrom
captain0xff:gizmo2
Feb 8, 2026
Merged

PartDesign: add interactive gizmos for box, cylinder and sphere operations#23700
chennes merged 2 commits intoFreeCAD:mainfrom
captain0xff:gizmo2

Conversation

@captain0xff
Copy link
Member

Implements interactive draggers for the box, cylinder and sphere primitive operations from the part design workbench.

@github-actions github-actions bot added Mod: Core Issue or PR touches core sections (App, Gui, Base) of FreeCAD Mod: Part Design Related to the Part Design Workbench Mod: Part Related to the Part Workbench labels Sep 6, 2025
@captain0xff
Copy link
Member Author

While not directly related, it might be better to fix #23675 before merging this PR as I wasn't able to test some cases due to that issue.

@maxwxyz maxwxyz added the Type: Feature FR for improvements or new features label Sep 6, 2025
@maxwxyz maxwxyz added this to the 1.2 milestone Sep 6, 2025
@maxwxyz
Copy link
Collaborator

maxwxyz commented Oct 26, 2025

@captain0xff the PR has a conflict

@captain0xff
Copy link
Member Author

fixed

@maxwxyz maxwxyz moved this from Queue to Merge Meeting in Merge Queue Nov 14, 2025
@maxwxyz maxwxyz added the Requires: Testing The PR needs testing by users and developers label Jan 9, 2026
Copy link
Collaborator

@maxwxyz maxwxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works!
grafik
grafik

Other tools could be added or segment angle for sphere later.

@maxwxyz maxwxyz added Approved: Tested The PR was manually tested and approved and removed Requires: Testing The PR needs testing by users and developers labels Feb 3, 2026
@maxwxyz maxwxyz removed the status in Merge Queue Feb 3, 2026
@maxwxyz maxwxyz moved this to Approved in Merge Queue Feb 3, 2026
@chennes
Copy link
Member

chennes commented Feb 3, 2026

Running with ASAN on yields the following reproducible crash when inserting a PD primitive with this PR:

==55093==ERROR: AddressSanitizer: stack-use-after-scope on address 0x00016aedd6e0 at pc 0x00010e44f310 bp 0x00016aedd4b0 sp 0x00016aedd4a8
READ of size 8 at 0x00016aedd6e0 thread T0
    #0 0x10e44f30c in Gui::GizmoContainer::addGizmos(std::initializer_list<Gui::Gizmo*>) Gizmo.cpp:683
    #1 0x10e450154 in Gui::GizmoContainer::create(std::initializer_list<Gui::Gizmo*>, Gui::ViewProviderDragger*) Gizmo.cpp:781
    #2 0x3103df518 in PartDesignGui::TaskBoxPrimitives::setupGizmos() TaskPrimitiveParameters.cpp:1037
    #3 0x3103b4278 in PartDesignGui::TaskBoxPrimitives::TaskBoxPrimitives(PartDesignGui::ViewProviderPrimitive*, QWidget*) TaskPrimitiveParameters.cpp:374
    #4 0x3103df708 in PartDesignGui::TaskBoxPrimitives::TaskBoxPrimitives(PartDesignGui::ViewProviderPrimitive*, QWidget*) TaskPrimitiveParameters.cpp:53
    #5 0x3103e68b0 in PartDesignGui::TaskDlgPrimitiveParameters::TaskDlgPrimitiveParameters(PartDesignGui::ViewProviderPrimitive*) TaskPrimitiveParameters.cpp:1077
    #6 0x3103e70d8 in PartDesignGui::TaskDlgPrimitiveParameters::TaskDlgPrimitiveParameters(PartDesignGui::ViewProviderPrimitive*) TaskPrimitiveParameters.cpp:1074
    #7 0x31052a780 in PartDesignGui::ViewProviderPrimitive::getEditDialog() ViewProviderPrimitive.cpp:55
    #8 0x3104a1270 in PartDesignGui::ViewProvider::setEdit(int) ViewProvider.cpp:167
    #9 0x10eb2deb0 in Gui::ViewProvider::startEditing(int) ViewProvider.cpp:147
    #10 0x10ebbe09c in Gui::ViewProviderDragger::startEditing(int) ViewProviderDragger.cpp:136
    #11 0x3104a3ae4 in PartDesignGui::ViewProvider::startEditing(int) ViewProvider.cpp:316
    #12 0x10d3dce08 in Gui::DocumentP::tryStartEditing(Gui::ViewProviderDocumentObject*, App::DocumentObject*, int) Document.cpp:287
    #13 0x10d3acaa4 in Gui::DocumentP::tryStartEditing(Gui::ViewProviderDocumentObject*, App::DocumentObject*, char const*, int) Document.cpp:280
    #14 0x10d3a95a8 in Gui::Document::trySetEdit(Gui::ViewProvider*, int, char const*) Document.cpp:682
    #15 0x10d3a8b10 in Gui::Document::setEdit(Gui::ViewProvider*, int, char const*) Document.cpp:608
    #16 0x10d4734a0 in Gui::DocumentPy::setEdit(_object*) DocumentPyImp.cpp:159
    #17 0x10d469a68 in Gui::DocumentPy::staticCallback_setEdit(_object*, _object*) DocumentPy.cpp:553
    #18 0x1057e6a6c in cfunction_call+0x118 (libpython3.11.dylib:arm64+0xb6a6c)
    #19 0x10578ee30 in _PyObject_MakeTpCall+0x148 (libpython3.11.dylib:arm64+0x5ee30)
    #20 0x10588c298 in _PyEval_EvalFrameDefault+0x4fd0 (libpython3.11.dylib:arm64+0x15c298)
    #21 0x105886320 in PyEval_EvalCode+0xc8 (libpython3.11.dylib:arm64+0x156320)
    #22 0x1058ea314 in run_mod+0x8c (libpython3.11.dylib:arm64+0x1ba314)
    #23 0x1058ee010 in PyRun_StringFlags+0x6c (libpython3.11.dylib:arm64+0x1be010)
    #24 0x10666b5e0 in Base::InterpreterSingleton::runString(char const*) Interpreter.cpp:248
    #25 0x10d70e1ac in Gui::Command::_runCommand(char const*, int, Gui::Command::DoCmd_Type, char const*) Command.cpp:784
    #26 0x10d81357c in void Gui::_cmdDocument<std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>>(Gui::Command::DoCmd_Type, App::Document const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>&&) CommandT.h:116
    #27 0x10d813dd4 in void Gui::_cmdDocument<std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>>(Gui::Command::DoCmd_Type, App::DocumentObject const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>&&) CommandT.h:257
    #28 0x10d8043b8 in void Gui::cmdGuiDocument<std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>>(App::DocumentObject const*, std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>&&) CommandT.h:322
    #29 0x3101a399c in PartDesignGui::setEdit(App::DocumentObject*, PartDesign::Body*) Utils.cpp:103
    #30 0x31011b6ac in CmdPrimtiveCompAdditive::activated(int) CommandPrimitive.cpp:150
    #31 0x10d70b6b4 in Gui::Command::_invoke(int, bool) Command.cpp:479
    #32 0x10d70ac30 in Gui::Command::invoke(int, Gui::Command::TriggerSource) Command.cpp:443
    #33 0x10d69d6a0 in Gui::ActionGroup::onActivated(QAction*) Action.cpp:593
    #34 0x10d6d6048 in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<QAction*>, void, void (Gui::ActionGroup::*)(QAction*)>::call(void (Gui::ActionGroup::*)(QAction*), Gui::ActionGroup*, void**)::'lambda'()::operator()() const qobjectdefs_impl.h:152
    #35 0x10d6d5dac in void QtPrivate::FunctorCallBase::call_internal<void, QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<QAction*>, void, void (Gui::ActionGroup::*)(QAction*)>::call(void (Gui::ActionGroup::*)(QAction*), Gui::ActionGroup*, void**)::'lambda'()>(void**, QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<QAction*>, void, void (Gui::ActionGroup::*)(QAction*)>::call(void (Gui::ActionGroup::*)(QAction*), Gui::ActionGroup*, void**)::'lambda'()&&) qobjectdefs_impl.h:65
    #36 0x10d6d5a84 in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<QAction*>, void, void (Gui::ActionGroup::*)(QAction*)>::call(void (Gui::ActionGroup::*)(QAction*), Gui::ActionGroup*, void**) qobjectdefs_impl.h:151
    #37 0x10d6d57cc in void QtPrivate::FunctionPointer<void (Gui::ActionGroup::*)(QAction*)>::call<QtPrivate::List<QAction*>, void>(void (Gui::ActionGroup::*)(QAction*), Gui::ActionGroup*, void**) qobjectdefs_impl.h:199
    #38 0x10d6d5620 in QtPrivate::QCallableObject<void (Gui::ActionGroup::*)(QAction*), QtPrivate::List<QAction*>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) qobjectdefs_impl.h:570
    #39 0x10af34be0 in void doActivate<false>(QObject*, int, void**)+0x56c (libQt6Core.6.8.3.dylib:arm64+0x1e8be0)
    #40 0x10b66b8b8 in QActionGroup::_q_actionTriggered()+0x58 (libQt6Gui.6.8.3.dylib:arm64+0x638b8)
    #41 0x10af34be0 in void doActivate<false>(QObject*, int, void**)+0x56c (libQt6Core.6.8.3.dylib:arm64+0x1e8be0)
    #42 0x10b750a20 in QAction::activate(QAction::ActionEvent)+0x114 (libQt6Gui.6.8.3.dylib:arm64+0x148a20)
    #43 0x10af2c848 in QObject::event(QEvent*)+0x134 (libQt6Core.6.8.3.dylib:arm64+0x1e0848)
    #44 0x107b989bc in QApplicationPrivate::notify_helper(QObject*, QEvent*)+0x148 (libQt6Widgets.6.8.3.dylib:arm64+0x3f89bc)
    #45 0x107b999b4 in QApplication::notify(QObject*, QEvent*)+0x208 (libQt6Widgets.6.8.3.dylib:arm64+0x3f99b4)
    #46 0x10d50c554 in Gui::GUIApplication::notify(QObject*, QEvent*) GuiApplication.cpp:103
    #47 0x10afdf344 in QCoreApplication::notifyInternal2(QObject*, QEvent*)+0xc0 (libQt6Core.6.8.3.dylib:arm64+0x293344)
    #48 0x10afe0a08 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*)+0x1fc (libQt6Core.6.8.3.dylib:arm64+0x294a08)
    #49 0x122257650 in QCocoaEventDispatcherPrivate::processPostedEvents()+0xb0 (libqcocoa.dylib:arm64+0x2f650)
    #50 0x12225899c in QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void*)+0x1d8 (libqcocoa.dylib:arm64+0x3099c)
    #51 0x186c8eb10 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__+0x18 (CoreFoundation:arm64+0x7cb10)
    #52 0x186c8eaa4 in __CFRunLoopDoSource0+0xa8 (CoreFoundation:arm64+0x7caa4)
    #53 0x186c8e810 in __CFRunLoopDoSources0+0xe4 (CoreFoundation:arm64+0x7c810)
    #54 0x186c8d464 in __CFRunLoopRun+0x344 (CoreFoundation:arm64+0x7b464)
    #55 0x186c8ca94 in CFRunLoopRunSpecific+0x238 (CoreFoundation:arm64+0x7aa94)
    #56 0x19272f278 in RunCurrentEventLoopInMode+0x140 (HIToolbox:arm64+0xc3278)
    #57 0x192732318 in ReceiveNextEventCommon+0xd4 (HIToolbox:arm64+0xc6318)
    #58 0x1928bd480 in _BlockUntilNextEventMatchingListInModeWithFilter+0x48 (HIToolbox:arm64+0x251480)
    #59 0x18abb1a30 in _DPSNextEvent+0x2a8 (AppKit:arm64+0x3aa30)
    #60 0x18b55093c in -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:]+0x2ac (AppKit:arm64+0x9d993c)
    #61 0x18aba4be0 in -[NSApplication run]+0x1dc (AppKit:arm64+0x2dbe0)
    #62 0x122256260 in QCocoaEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>)+0x948 (libqcocoa.dylib:arm64+0x2e260)
    #63 0x10afe00e4 in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>)+0x234 (libQt6Core.6.8.3.dylib:arm64+0x2940e4)
    #64 0x10afdfd30 in QCoreApplication::exec()+0x88 (libQt6Core.6.8.3.dylib:arm64+0x293d30)
    #65 0x10d19e5c8 in (anonymous namespace)::tryRunEventLoop(Gui::GUISingleApplication&) Application.cpp:2463
    #66 0x10d148684 in (anonymous namespace)::runEventLoop(Gui::GUISingleApplication&) Application.cpp:2496
    #67 0x10d146f5c in Gui::Application::runApplication() Application.cpp:2609
    #68 0x104f1a55c in main MainGui.cpp:335
    #69 0x186802b94  (<unknown module>)

Address 0x00016aedd6e0 is located in stack of thread T0 at offset 128 in frame
    #0 0x3103dec44 in PartDesignGui::TaskBoxPrimitives::setupGizmos() TaskPrimitiveParameters.cpp:1010

  This frame has 5 object(s):
    [32, 48) 'list' (line 1015)
    [64, 88) 'ref.tmp20' (line 1022)
    [128, 144) 'ref.tmp40' (line 1028) <== Memory access at offset 128 is inside this variable
    [160, 168) 'ref.tmp57' (line 1033)
    [192, 200) 'ref.tmp63' (line 1037)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope Gizmo.cpp:683 in Gui::GizmoContainer::addGizmos(std::initializer_list<Gui::Gizmo*>)
Shadow bytes around the buggy address:
  0x00016aedd400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00016aedd480: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 f3 f3
  0x00016aedd500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00016aedd580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00016aedd600: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
=>0x00016aedd680: 00 00 f2 f2 f8 f8 f8 f2 f2 f2 f2 f2[f8]f8 f2 f2
  0x00016aedd700: f8 f2 f2 f2 00 f3 f3 f3 00 00 00 00 00 00 00 00
  0x00016aedd780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00016aedd800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00016aedd880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x00016aedd900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==55093==ABORTING

@chennes chennes added the Requested changes: Testing The PR was manually tested and issues were found label Feb 3, 2026
@maxwxyz
Copy link
Collaborator

maxwxyz commented Feb 3, 2026

@chennes how did you get it to crash? It worked without issues here. Do you have a file?

@chennes
Copy link
Member

chennes commented Feb 3, 2026

No file, @maxwxyz -- I created a new file, loaded Part Design, and tried to add a cylinder primitive. I get the crash and you don't because it's a subtle memory problem that is only being exposed because I'm running with the Address Sanitizer (ASAN) turned on during compilation.

@maxwxyz
Copy link
Collaborator

maxwxyz commented Feb 3, 2026

can we run ASAN on the GUI tests in CI then to catch such thing?
Otherwise it does not make that much sense to manually test it if it looks good but is actually bad.

@chennes
Copy link
Member

chennes commented Feb 3, 2026

Probably -- it's just a matter of adding

-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_LD_FLAGS="-fsanitize=address -fno-omit-frame-pointer"

to the CMake configure command line (but I don't actually know where that is now...). That said, it only matters if the GUI test actually tests the gizmos.

@captain0xff captain0xff force-pushed the gizmo2 branch 2 times, most recently from 2877563 to 02fc5d9 Compare February 4, 2026 16:02
@captain0xff
Copy link
Member Author

The latest commit should fix the problem. @chennes can you kindly test?
Ideally I should remove the initializer list but I have some refactors planned for the gizmos and might do this as part of that since this will touch every operation for which the gizmos are implemented.

@chennes
Copy link
Member

chennes commented Feb 8, 2026

Works perfectly now, thanks!

@chennes chennes merged commit 3237a21 into FreeCAD:main Feb 8, 2026
13 checks passed
@github-project-automation github-project-automation bot moved this from Approved to Done in Merge Queue Feb 8, 2026
@captain0xff captain0xff deleted the gizmo2 branch February 9, 2026 21:18
Reqrefusion pushed a commit to Reqrefusion/FreeCAD-Documentation-Project that referenced this pull request Feb 12, 2026
…zia [[Image:PartDesign_Draft.svg|24px]] [[PartDesign_Draft/pl|Pochylenie ścian]]. [FreeCAD/FreeCAD#27111 Pull request #27111] * Interaktywne manipulatory zostały dodane do narzędzi Sfera, Prostopadłościan oraz Walec. [FreeCAD/FreeCAD#23700 Pull request #23700]"
Reqrefusion pushed a commit to Reqrefusion/FreeCAD-Documentation-html that referenced this pull request Feb 12, 2026
…zia [[Image:PartDesign_Draft.svg|24px]] [[PartDesign_Draft/pl|Pochylenie ścian]]. [FreeCAD/FreeCAD#27111 Pull request #27111] * Interaktywne manipulatory zostały dodane do narzędzi Sfera, Prostopadłościan oraz Walec. [FreeCAD/FreeCAD#23700 Pull request #23700]"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved: Tested The PR was manually tested and approved Mod: Core Issue or PR touches core sections (App, Gui, Base) of FreeCAD Mod: Part Design Related to the Part Design Workbench Mod: Part Related to the Part Workbench Requested changes: Testing The PR was manually tested and issues were found Type: Feature FR for improvements or new features

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants