3D Widgets

From ParaQ Wiki
Jump to navigationJump to search

Adding a (new style) 3D widget to SM requires writing 3 XML proxy definitions. Here is an example:

  • Widget: The widget takes care of event handling and passing the right events to the representation. This is only instantiated on the client:
 
    <Proxy name="SliderWidget" class="vtkSliderWidget">
      <ProxyProperty name="Representation"
         command="SetRepresentation">
        <ProxyGroupDomain name="groups">
          <Group name="props" />
        </ProxyGroupDomain>
      </ProxyProperty>
      <IntVectorProperty name="Enabled" 
                         command="SetEnabled"
                         number_of_elements="1"
                         default_values="0">
        <BooleanDomain/>
      </IntVectorProperty>
    </Proxy>

Here, the Enabled property is required.

  • Representation: Representation contains the actual geometry that is rendered and that is used for picking
    <Proxy name="SliderRepresentation3D" class="vtkSliderRepresentation3D">
      <IntVectorProperty
        name="Visibility"
        command="SetVisibility"
        number_of_elements="1"
        default_values="1"
        animateable="1">
        <BooleanDomain name="bool" />
      </IntVectorProperty>

     <DoubleVectorProperty 
        name="Value"
        command="SetValue"
        number_of_elements="1"
        information_property="ValueInfo"
        default_values="0">
        <DoubleRangeDomain name="range"/>
     </DoubleVectorProperty>

     <DoubleVectorProperty 
        name="ValueInfo"
        command="GetValue"
        information_only="1">
        <SimpleDoubleInformationHelper/>
     </DoubleVectorProperty>

      <DoubleVectorProperty
        name="Point1"
        command="SetPoint1InWorldCoordinates"
        number_of_elements="3"
        default_values="-1.0 0.0 0.0">
        <DoubleRangeDomain name="range"/>
      </DoubleVectorProperty>

      <DoubleVectorProperty
        name="Point2"
        command="SetPoint2InWorldCoordinates"
        number_of_elements="3"
        default_values="1.0 0.0 0.0">
        <DoubleRangeDomain name="range"/>
      </DoubleVectorProperty>

     <DoubleVectorProperty 
        name="MinimumValue"
        command="SetMinimumValue"
        number_of_elements="1"
        default_values="0">
        <DoubleRangeDomain name="range"/>
     </DoubleVectorProperty>

     <DoubleVectorProperty 
        name="MaximumValue"
        command="SetMaximumValue"
        number_of_elements="1"
        default_values="1">
        <DoubleRangeDomain name="range"/>
     </DoubleVectorProperty>
    </Proxy>

The Visibility property is required.

  • Finally:
    <New3DWidgetProxy name="SliderRepresentation">
      <SubProxy>
        <Proxy name="Prop"
          proxygroup="props" proxyname="SliderRepresentation3D">
        </Proxy>
        <ExposedProperties>
          <Property name="Visibility" />
          <Property name="ValueInfo" />
          <Property name="Value" />
          <Property name="Point1" />
          <Property name="Point2" />
          <Property name="MinimumValue" />
          <Property name="MaximumValue" />
        </ExposedProperties>
      </SubProxy>

      <SubProxy>
        <Proxy name="Widget"
          proxygroup="3d_widgets" proxyname="SliderWidget">
        </Proxy>
        <ExposedProperties>
          <Property name="Enabled" />
        </ExposedProperties>
      </SubProxy>
    </New3DWidgetProxy>
    <!-- End of displays group -->
  </ProxyGroup>

The vtkSMNew3DWidgetProxy class takes care of managing the 3d widgets. It has to have 2 sub-proxies: Prop and Widget. These two must correspond to a vtkWidgetRepresentation and vtkAbstractWidget (like the two examples above). The Visibility and Enabled properties must be enabled. Once added to the render module, a vtkSMNew3DWidgetProxy object manages the widget and the representation. If a user interaction causes a change to the representation it manages, it updates the corresponding property. For this, one or more pairs of properties have to exist in the representation. For example:

    <Proxy name="SliderRepresentation3D" class="vtkSliderRepresentation3D">
     ...
     <DoubleVectorProperty 
        name="Value"
        command="SetValue"
        number_of_elements="1"
        information_property="ValueInfo"
        default_values="0">
        <DoubleRangeDomain name="range"/>
     </DoubleVectorProperty>

     <DoubleVectorProperty 
        name="ValueInfo"
        command="GetValue"
        information_only="1">
        <SimpleDoubleInformationHelper/>
     </DoubleVectorProperty>
     ...
   </Proxy>

Here, ValueInfo is responsible of getting Value from the (client-side) representation. Value and ValueInfo are linked (by the proxy at creation) so this change will be automatically reflected to the Value property. All the GUI has to do is to listen to the PropertyModifiedEvent and grab changes from the Value property. The GUI can also set the Value directly (make sure to call render afterwards otherwise the widget won't change). Any property pair defined liked this is automatically linked by the proxy at creation. All properties are updated when a EndInteractionEvent is invoked by the widget.

Here is how this all comes together in the example widget I added to paraview (GUI/Widget/vtkPVNew3DWidget.cxx). First, create the proxy and add observer:

  this->WidgetProxy = vtkSMNew3DWidgetProxy::SafeDownCast(
    vtkSMObject::GetProxyManager()->NewProxy(
      "displays", "SliderRepresentation"));

  // Force object creation
  this->WidgetProxy->UpdateVTKObjects();
  this->WidgetProxy->AddObserver(vtkCommand::PropertyModifiedEvent,
                                         this->Observer);
  // Add to the render module.
  vtkSMRenderModuleProxy* rm = pvApp->GetRenderModuleProxy();
  vtkSMProxyProperty* pp = vtkSMProxyProperty::SafeDownCast(
    rm->GetProperty("Displays"));
  pp->AddProxy(this->WidgetProxy);
  rm->UpdateVTKObjects();


Initialize the GUI and the widget from the property that is controlled (Resolution of the cone):

void vtkPVNew3DWidget::Initialize()
{
  vtkSMIntVectorProperty* ivp = vtkSMIntVectorProperty::SafeDownCast(
    this->GetSMProperty());
  this->EntryWidget->SetValueAsInt(ivp->GetElement(0));
  
  vtkSMDoubleVectorProperty* dvp = vtkSMDoubleVectorProperty::SafeDownCast(
    this->WidgetProxy->GetProperty("Value"));
  dvp->SetElements1(ivp->GetElement(0));
  this->WidgetProxy->UpdateVTKObjects();
  this->GetPVApplication()->GetRenderModuleProxy()->StillRender();
}

Switch the widget on/off when the cone source proxy is selected/deselected:

//----------------------------------------------------------------------------
void vtkPVNew3DWidget::Select()
{
  if(this->WidgetProxy)
    {
    vtkSMIntVectorProperty* ivp = vtkSMIntVectorProperty::SafeDownCast(
      this->WidgetProxy->GetProperty("Visibility"));
    ivp->SetElements1(1);

    vtkSMIntVectorProperty* enabled = vtkSMIntVectorProperty::SafeDownCast(
      this->WidgetProxy->GetProperty("Enabled"));
    enabled->SetElements1(1);

    this->WidgetProxy->UpdateVTKObjects();
    }
}

//----------------------------------------------------------------------------
void vtkPVNew3DWidget::Deselect()
{
  if(this->WidgetProxy)
    {
    vtkSMIntVectorProperty* ivp = vtkSMIntVectorProperty::SafeDownCast(
      this->WidgetProxy->GetProperty("Visibility"));
    ivp->SetElements1(0);

    vtkSMIntVectorProperty* enabled = vtkSMIntVectorProperty::SafeDownCast(
      this->WidgetProxy->GetProperty("Enabled"));
    enabled->SetElements1(0);

    this->WidgetProxy->UpdateVTKObjects();
    }
}

If the 3D widget is modified, update the corresponding entry widget:

void vtkPVNew3DWidget::WidgetModified()
{
  vtkSMDoubleVectorProperty* dvp = vtkSMDoubleVectorProperty::SafeDownCast(
    this->WidgetProxy->GetProperty("Value"));
  if (!dvp)
    {
    return;
    }

  double val = dvp->GetElement(0);
  if (val != this->EntryWidget->GetValueAsDouble())
    {
    this->EntryWidget->SetValueAsDouble(val);
    this->ModifiedCallback();
    }
}

If the entry widget is modified, update the 3D widget:

void vtkPVNew3DWidget::EntryModifiedCallback(const char* key)
{
  vtkSMDoubleVectorProperty* dvp = vtkSMDoubleVectorProperty::SafeDownCast(
    this->WidgetProxy->GetProperty("Value"));
  if (!dvp)
    {
    return;
    }

  if ((key && (!strcmp(key, "Tab") ||
              !strcmp(key, "ISO_Left_Tab") ||
              !strcmp(key, "Return") ||
              !strcmp(key, ""))) || !key)
    {
    double val = dvp->GetElement(0);
    double eVal = this->EntryWidget->GetValueAsDouble();
    if (val != eVal)
      {
      dvp->SetElements1(eVal);
      this->WidgetProxy->UpdateVTKObjects();
      this->GetPVApplication()->GetRenderModuleProxy()->StillRender();
      this->ModifiedCallback();
      }
    }
}

The important thing to remember here is the fact that there are two sets of properties that we are dealing with: the ones from the 3D widget, the ones that are controlled (in the case the resolution of the cone). Changes to the 3D widget are immediately pulled/pushed to sync it with the entry, whereas the values are pushed to the controlled proxies only at Accept:

void vtkPVNew3DWidget::Accept()
{
  vtkSMIntVectorProperty* ivp = vtkSMIntVectorProperty::SafeDownCast(
    this->GetSMProperty());
  ivp->SetElements1(this->EntryWidget->GetValueAsInt());

  this->Superclass::Accept();
}

The Accept/Reset part is trivial and is already handled by ParaQ. A new mechanism that can connect a ParaQ widget to a 3D widget proxy has to be added to make it all work.