ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvConstrainedDistanceWidget.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 //
8 // 控制层实现 - 从 ParaView vtkLineWidget2.cxx 直接移植
9 
11 
13 
14 // CV_CORE_LIB
15 #include <CVLog.h>
16 
17 // VTK
18 #include <vtkCallbackCommand.h>
19 #include <vtkCommand.h>
20 #include <vtkHandleRepresentation.h>
21 #include <vtkLineRepresentation.h>
22 #include <vtkMath.h>
23 #include <vtkObjectFactory.h>
24 #include <vtkPointHandleRepresentation3D.h>
25 #include <vtkRenderWindowInteractor.h>
26 #include <vtkVersion.h>
27 
28 // STL
29 #include <algorithm>
30 #include <cctype>
31 #include <cmath>
32 #include <string>
33 
35 
36 //------------------------------------------------------------------------------
38  : KeyPressObserverId(0),
39  KeyReleaseObserverId(0),
40  KeyboardCallbackCommand(nullptr) {}
41 
42 //------------------------------------------------------------------------------
44  if (this->Interactor) {
45  if (this->KeyPressObserverId) {
46  this->Interactor->RemoveObserver(this->KeyPressObserverId);
47  }
48  if (this->KeyReleaseObserverId) {
49  this->Interactor->RemoveObserver(this->KeyReleaseObserverId);
50  }
51  }
52  if (this->KeyboardCallbackCommand) {
53  this->KeyboardCallbackCommand->Delete();
54  this->KeyboardCallbackCommand = nullptr;
55  }
56 }
57 
58 //------------------------------------------------------------------------------
59 // Following ParaView vtkLineWidget2::SetInteractor pattern
61  vtkRenderWindowInteractor* iren) {
62  // Call superclass first
63  this->Superclass::SetInteractor(iren);
64 }
65 
66 //------------------------------------------------------------------------------
68  // CRITICAL: We need to prevent the parent class (vtkLineWidget2) from
69  // registering its own keyboard event handler, which conflicts with ours.
70  // The parent class registers its keyboard handler in SetEnabled(1).
71 
72  if (enabling && this->Interactor && this->KeyPressObserverId == 0) {
73  // Register our custom keyboard event observers BEFORE calling parent
74  // SetEnabled
75  if (!this->KeyboardCallbackCommand) {
76  this->KeyboardCallbackCommand = vtkCallbackCommand::New();
77  this->KeyboardCallbackCommand->SetCallback(
79  this->KeyboardCallbackCommand->SetClientData(this);
80  }
81 
82  // Use high priority (10.0) to ensure our handler is called first
83  this->KeyPressObserverId = this->Interactor->AddObserver(
84  vtkCommand::KeyPressEvent, this->KeyboardCallbackCommand, 10.0);
85  this->KeyReleaseObserverId = this->Interactor->AddObserver(
86  vtkCommand::KeyReleaseEvent, this->KeyboardCallbackCommand,
87  10.0);
88  }
89 
90  // Call parent SetEnabled (this does necessary initialization for widget
91  // interaction)
92  this->Superclass::SetEnabled(enabling);
93 
94  if (enabling && this->Interactor) {
95  // CRITICAL: After parent SetEnabled, parent class (vtkLineWidget2) has
96  // registered its own keyboard observers. We need to remove ALL keyboard
97  // observers and keep only ours. Save our observer IDs first
98  unsigned long savedKeyPress = this->KeyPressObserverId;
99  unsigned long savedKeyRelease = this->KeyReleaseObserverId;
100  vtkCallbackCommand* savedCallback = this->KeyboardCallbackCommand;
101 
102  // Remove ALL keyboard observers (including parent's problematic ones)
103  this->Interactor->RemoveObservers(vtkCommand::KeyPressEvent);
104  this->Interactor->RemoveObservers(vtkCommand::KeyReleaseEvent);
105 
106  // Re-add ONLY our observers with high priority
107  if (savedCallback) {
108  this->KeyPressObserverId = this->Interactor->AddObserver(
109  vtkCommand::KeyPressEvent, savedCallback, 10.0);
110  this->KeyReleaseObserverId = this->Interactor->AddObserver(
111  vtkCommand::KeyReleaseEvent, savedCallback, 10.0);
112  }
113  }
114 
115  if (!enabling && this->Interactor) {
116  // Remove our custom keyboard event observers
117  if (this->KeyPressObserverId) {
118  this->Interactor->RemoveObserver(this->KeyPressObserverId);
119  this->KeyPressObserverId = 0;
120  }
121  if (this->KeyReleaseObserverId) {
122  this->Interactor->RemoveObserver(this->KeyReleaseObserverId);
123  this->KeyReleaseObserverId = 0;
124  }
125  // Parent class should clean up its observers in its SetEnabled(0)
126  // Our ProcessKeyEvents has strong safety checks to prevent crashes
127  // even if parent's observers are somehow still called
128  }
129 }
130 
131 //------------------------------------------------------------------------------
133  unsigned long event,
134  void* clientdata,
135  void* calldata) {
137  static_cast<cvConstrainedDistanceWidget*>(clientdata);
138 
139  // CRITICAL Safety checks: ensure widget is in valid state
140  // This prevents crashes when callbacks are triggered during/after
141  // destruction
142  if (!self) {
143  return; // Widget destroyed
144  }
145 
146  // Extra safety: check if widget is being destroyed
147  try {
148  // Try to access a member - if widget is deleted, this may throw or
149  // crash
150  if (!self->GetEnabled()) {
151  return; // Widget disabled - don't process any events
152  }
153  } catch (...) {
154  return; // Widget in invalid state
155  }
156 
157  if (!self->Interactor || !self->WidgetRep) {
158  return; // Widget not fully initialized or already destroyed
159  }
160 
161  if (!self->GetProcessEvents()) {
162  return; // Widget locked - don't process events
163  }
164 
165  vtkLineRepresentation* rep =
166  vtkLineRepresentation::SafeDownCast(self->WidgetRep);
167  if (!rep) {
168  return;
169  }
170 
171  // CRITICAL: Safe access to Interactor - may be null during destruction
172  char* cKeySym = nullptr;
173  try {
174  if (self->Interactor) {
175  cKeySym = self->Interactor->GetKeySym();
176  }
177  } catch (...) {
178  return; // Interactor in invalid state
179  }
180 
181  if (!cKeySym) {
182  return; // No key symbol available
183  }
184 
185  std::string keySym = cKeySym;
186  std::transform(keySym.begin(), keySym.end(), keySym.begin(), ::toupper);
187 
188  // Only handle X, Y, Z, L keys - ignore all other keys to prevent conflicts
189  // with application shortcuts (like 'P' for picking)
190  bool isConstraintKey =
191  (keySym == "X" || keySym == "Y" || keySym == "Z" || keySym == "L");
192  if (!isConstraintKey) {
193  return; // Let other keys be handled by application shortcuts (don't
194  // abort)
195  }
196 
197  if (event == vtkCommand::KeyPressEvent) {
198  if (keySym == "X") {
199  rep->GetPoint1Representation()->SetXTranslationAxisOn();
200  rep->GetPoint2Representation()->SetXTranslationAxisOn();
201  rep->GetLineHandleRepresentation()->SetXTranslationAxisOn();
202  rep->GetPoint1Representation()->SetConstrained(true);
203  rep->GetPoint2Representation()->SetConstrained(true);
204  rep->GetLineHandleRepresentation()->SetConstrained(true);
205  } else if (keySym == "Y") {
206  rep->GetPoint1Representation()->SetYTranslationAxisOn();
207  rep->GetPoint2Representation()->SetYTranslationAxisOn();
208  rep->GetLineHandleRepresentation()->SetYTranslationAxisOn();
209  rep->GetPoint1Representation()->SetConstrained(true);
210  rep->GetPoint2Representation()->SetConstrained(true);
211  rep->GetLineHandleRepresentation()->SetConstrained(true);
212  } else if (keySym == "Z") {
213  rep->GetPoint1Representation()->SetZTranslationAxisOn();
214  rep->GetPoint2Representation()->SetZTranslationAxisOn();
215  rep->GetLineHandleRepresentation()->SetZTranslationAxisOn();
216  rep->GetPoint1Representation()->SetConstrained(true);
217  rep->GetPoint2Representation()->SetConstrained(true);
218  rep->GetLineHandleRepresentation()->SetConstrained(true);
219  } else if (keySym == "L") {
220  // Custom translation axis along the line direction
221  // This works with all VTK versions using
222  // cvCustomAxisHandleRepresentation
223  double p1[3], p2[3], v[3];
224  rep->GetPoint1WorldPosition(p1);
225  rep->GetPoint2WorldPosition(p2);
226  vtkMath::Subtract(p2, p1, v);
227  vtkMath::Normalize(v);
228 
229  // Try to cast to cvCustomAxisHandleRepresentation
230  auto* h1 = dynamic_cast<cvCustomAxisHandleRepresentation*>(
231  rep->GetPoint1Representation());
232  auto* h2 = dynamic_cast<cvCustomAxisHandleRepresentation*>(
233  rep->GetPoint2Representation());
234  auto* hLine = dynamic_cast<cvCustomAxisHandleRepresentation*>(
235  rep->GetLineHandleRepresentation());
236 
237  if (h1 && h2 && hLine) {
238  // Use custom axis handle representation (works in all VTK
239  // versions)
241  h1->SetCustomTranslationAxis(v);
242  h2->SetCustomTranslationAxisOn();
243  h2->SetCustomTranslationAxis(v);
244  hLine->SetCustomTranslationAxisOn();
245  hLine->SetCustomTranslationAxis(v);
246  h1->SetConstrained(true);
247  h2->SetConstrained(true);
248  hLine->SetConstrained(true);
249  } else {
250  // Fallback: use base class methods if available
251 #if (VTK_MAJOR_VERSION > 9) || \
252  (VTK_MAJOR_VERSION == 9 && VTK_MINOR_VERSION >= 3)
253  // VTK 9.3+ has native support
254  rep->GetPoint1Representation()->SetCustomTranslationAxisOn();
255  rep->GetPoint1Representation()->SetCustomTranslationAxis(v);
256  rep->GetPoint2Representation()->SetCustomTranslationAxisOn();
257  rep->GetPoint2Representation()->SetCustomTranslationAxis(v);
258  rep->GetLineHandleRepresentation()
259  ->SetCustomTranslationAxisOn();
260  rep->GetLineHandleRepresentation()->SetCustomTranslationAxis(v);
261  rep->GetPoint1Representation()->SetConstrained(true);
262  rep->GetPoint2Representation()->SetConstrained(true);
263  rep->GetLineHandleRepresentation()->SetConstrained(true);
264 #else
265  // For older VTK without custom handles, log a warning
267  "L key: Custom axis handle representation not in use. "
268  "Please use cvCustomAxisHandleRepresentation for full "
269  "functionality.");
270 #endif
271  }
272  }
273  } else if (event == vtkCommand::KeyReleaseEvent) {
274  if (keySym == "L" || keySym == "X" || keySym == "Y" || keySym == "Z") {
275  // Try to cast to cvCustomAxisHandleRepresentation first
276  auto* h1 = dynamic_cast<cvCustomAxisHandleRepresentation*>(
277  rep->GetPoint1Representation());
278  auto* h2 = dynamic_cast<cvCustomAxisHandleRepresentation*>(
279  rep->GetPoint2Representation());
280  auto* hLine = dynamic_cast<cvCustomAxisHandleRepresentation*>(
281  rep->GetLineHandleRepresentation());
282 
283  if (h1 && h2 && hLine) {
284  // Use custom axis handle representation
285  if (keySym == "L") {
287  h2->SetCustomTranslationAxisOff();
288  hLine->SetCustomTranslationAxisOff();
289  }
290  // For X/Y/Z, SetTranslationAxis(-1) is called by base class
291  h1->SetTranslationAxis(-1);
292  h2->SetTranslationAxis(-1);
293  hLine->SetTranslationAxis(-1);
294  } else {
295  // Fallback to standard methods
296 #if (VTK_MAJOR_VERSION > 9) || \
297  (VTK_MAJOR_VERSION == 9 && VTK_MINOR_VERSION >= 3)
298  // VTK 9.3+ has SetTranslationAxisOff()
299  rep->GetPoint1Representation()->SetTranslationAxisOff();
300  rep->GetPoint2Representation()->SetTranslationAxisOff();
301  rep->GetLineHandleRepresentation()->SetTranslationAxisOff();
302 #else
303  // VTK 9.2: Use SetTranslationAxis(-1) to disable
304  rep->GetPoint1Representation()->SetTranslationAxis(-1);
305  rep->GetPoint2Representation()->SetTranslationAxis(-1);
306  rep->GetLineHandleRepresentation()->SetTranslationAxis(-1);
307 #endif
308  }
309 
310  rep->GetPoint1Representation()->SetConstrained(false);
311  rep->GetPoint2Representation()->SetConstrained(false);
312  rep->GetLineHandleRepresentation()->SetConstrained(false);
313  }
314  }
315 }
316 
317 //------------------------------------------------------------------------------
318 void cvConstrainedDistanceWidget::PrintSelf(ostream& os, vtkIndent indent) {
319  this->Superclass::PrintSelf(os, indent);
320 }
MouseEvent event
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
Line/Distance Widget with XYZL constraint support (100% consistent with ParaView)
void SetInteractor(vtkRenderWindowInteractor *iren) override
Set interactor - override to register keyboard events Copied from ParaView vtkLineWidget2.
static void ProcessKeyEvents(vtkObject *object, unsigned long event, void *clientdata, void *calldata)
Keyboard event handler - directly copied from ParaView vtkLineWidget2::ProcessKeyEvents.
void PrintSelf(ostream &os, vtkIndent indent) override
void SetEnabled(int enabling) override
Override SetEnabled to manage keyboard event observers We need to remove the parent class's keyboard ...
Handle representation with custom translation axis support.
void SetCustomTranslationAxisOff()
Disable custom translation axis mode.
void SetCustomTranslationAxisOn()
Enable custom translation axis mode.
vtkStandardNewMacro(cvConstrainedDistanceWidget)