Commit 54cd7a79 authored by Utkarsh Ayachit's avatar Utkarsh Ayachit

Box widget: improve box specification

Box implicit function was specified using a transform relative the data
bounds which were not exposed in the UI. This made it extremely tricky
to specify the box using absolute position/orientation/scale parameters.
Fixed that.

By default, now the box is always placed using absolute values i.e.
relative to a unit box anchored at origin. Added a mode that enables
user to provide an explicit bounding box to use for reference instead.
This will make it possible to support legacy state files.
parent 683ef288
# Improvements to Box implicit function specification
**Box** is one of the supported implicit functions for filters like **Clip**,
and **Slice**. The box is now specified using position, rotation and scale
parameters using global coordinate space rather than relative to the input
dataset bounds. We also support specifying the box parameters relative an
arbitraty bounding box which can now be explicitly specified in the UI.
......@@ -627,7 +627,7 @@ struct Process_5_6_to_5_7
bool operator()(xml_document& document)
{
return ConvertResampleWithDataset(document) && ConvertIdsFilter(document) &&
ConvertOSPRayNames(document);
ConvertOSPRayNames(document) && ConvertBox(document);
}
static bool ConvertResampleWithDataset(xml_document& document)
......@@ -692,6 +692,28 @@ struct Process_5_6_to_5_7
return true;
};
static bool ConvertBox(xml_document& document)
{
pugi::xpath_node_set elements = document.select_nodes(
"//ServerManagerState/Proxy[@group='implicit_functions' and @type='Box']");
for (auto iter = elements.begin(); iter != elements.end(); ++iter)
{
pugi::xml_node proxy_node = iter->node();
std::string id_string(proxy_node.attribute("id").value());
// add a `UseReferenceBounds=1` property to each one.
auto node = proxy_node.append_child("Property");
node.append_attribute("name").set_value("UseReferenceBounds");
node.append_attribute("id").set_value((id_string + ".UseReferenceBounds").c_str());
node.append_attribute("number_of_elements").set_value("1");
auto elem = node.append_child("Element");
elem.append_attribute("index").set_value("0");
elem.append_attribute("value").set_value("1");
}
return true;
}
};
} // end of namespace
......
......@@ -707,16 +707,35 @@
</Proxy>
<Proxy class="vtkPVBox" name="Box">
<InputProperty is_internal="1" name="Input" />
<DoubleVectorProperty command="SetBounds"
<IntVectorProperty name="UseReferenceBounds"
command="SetUseReferenceBounds"
number_of_elements="1"
default_values="0">
<BooleanDomain name="bool" />
</IntVectorProperty>
<DoubleVectorProperty command="SetReferenceBounds"
default_values="0 1 0 1 0 1"
name="Bounds"
number_of_elements="6"></DoubleVectorProperty>
number_of_elements="6">
<BoundsDomain mode="data_bounds" name="range">
<RequiredProperties>
<Property function="Input" name="Input" />
</RequiredProperties>
</BoundsDomain>
<Hints>
<NoDefault />
</Hints>
</DoubleVectorProperty>
<DoubleVectorProperty animateable="1"
command="SetPosition"
default_values="0.0 0.0 0.0"
name="Position"
number_of_elements="3">
<DoubleRangeDomain name="range" />
<BoundsDomain default_mode="min" mode="normal" name="range">
<RequiredProperties>
<Property function="Input" name="Input" />
</RequiredProperties>
</BoundsDomain>
<Documentation>
Set the position of the box.
</Documentation>
......@@ -736,7 +755,11 @@
default_values="1.0 1.0 1.0"
name="Scale"
number_of_elements="3">
<DoubleRangeDomain name="range" />
<BoundsDomain default_mode="max" mode="component_magnitude" name="range">
<RequiredProperties>
<Property function="Input" name="Input" />
</RequiredProperties>
</BoundsDomain>
<Documentation>
Set the size of the box via a scale factor.
</Documentation>
......@@ -747,6 +770,7 @@
<Property function="Scale" name="Scale" />
<Property function="PlaceWidget" name="Bounds" />
<Property function="Input" name="Input" />
<Property function="UseReferenceBounds" name="UseReferenceBounds" />
</PropertyGroup>
<Hints>
<ProxyList>
......
......@@ -14,9 +14,12 @@
=========================================================================*/
#include "vtkPVBox.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkTransform.h"
#include <algorithm>
vtkStandardNewMacro(vtkPVBox);
//----------------------------------------------------------------------------
vtkPVBox::vtkPVBox()
......@@ -24,6 +27,9 @@ vtkPVBox::vtkPVBox()
this->Position[0] = this->Position[1] = this->Position[2] = 0.0;
this->Rotation[0] = this->Rotation[1] = this->Rotation[2] = 0.0;
this->Scale[0] = this->Scale[1] = this->Scale[2] = 1.0;
vtkMath::UninitializeBounds(this->ReferenceBounds);
this->UseReferenceBounds = false;
}
//----------------------------------------------------------------------------
......@@ -31,34 +37,81 @@ vtkPVBox::~vtkPVBox()
{
}
//----------------------------------------------------------------------------
void vtkPVBox::SetUseReferenceBounds(bool val)
{
if (this->UseReferenceBounds != val)
{
this->UseReferenceBounds = val;
this->UpdateTransform();
this->Modified();
}
}
//----------------------------------------------------------------------------
void vtkPVBox::SetReferenceBounds(const double bds[6])
{
if (!std::equal(bds, bds + 6, this->ReferenceBounds))
{
std::copy(bds, bds + 6, this->ReferenceBounds);
this->UpdateTransform();
this->Modified();
}
}
//----------------------------------------------------------------------------
void vtkPVBox::SetPosition(const double pos[3])
{
memcpy(this->Position, pos, sizeof(double) * 3);
this->UpdateTransform();
this->Modified();
if (!std::equal(pos, pos + 3, this->Position))
{
std::copy(pos, pos + 3, this->Position);
this->UpdateTransform();
this->Modified();
}
}
//----------------------------------------------------------------------------
void vtkPVBox::SetRotation(const double pos[3])
{
memcpy(this->Rotation, pos, sizeof(double) * 3);
this->UpdateTransform();
this->Modified();
if (!std::equal(pos, pos + 3, this->Rotation))
{
std::copy(pos, pos + 3, this->Rotation);
this->UpdateTransform();
this->Modified();
}
}
//----------------------------------------------------------------------------
void vtkPVBox::SetScale(const double pos[3])
{
memcpy(this->Scale, pos, sizeof(double) * 3);
this->UpdateTransform();
this->Modified();
if (!std::equal(pos, pos + 3, this->Scale))
{
std::copy(pos, pos + 3, this->Scale);
this->UpdateTransform();
this->Modified();
}
}
//----------------------------------------------------------------------------
void vtkPVBox::UpdateTransform()
{
vtkTransform* trans = vtkTransform::New();
auto trans = vtkTransform::SafeDownCast(this->GetTransform());
if (trans == nullptr)
{
trans = vtkTransform::New();
this->SetTransform(trans);
trans->Delete();
}
if (this->UseReferenceBounds)
{
this->SetBounds(this->ReferenceBounds);
}
else
{
this->SetBounds(0, 1, 0, 1, 0, 1);
}
trans->Identity();
trans->Translate(this->Position);
trans->RotateZ(this->Rotation[2]);
......@@ -66,8 +119,6 @@ void vtkPVBox::UpdateTransform()
trans->RotateY(this->Rotation[1]);
trans->Scale(this->Scale);
trans->Inverse();
this->SetTransform(trans);
trans->Delete();
}
//----------------------------------------------------------------------------
......
/*=========================================================================
Program: ParaView
Module: $RCSfile$
Module: vtkPVBox
Copyright (c) Kitware, Inc.
All rights reserved.
......@@ -13,11 +13,13 @@
=========================================================================*/
/**
* @class vtkPVBox
* @brief extends vtkBox to add ParaView specific API.
* @class vtkPVBox
* @brief extends vtkBox to add ParaView specific API.
*
* vtkPVBox extends vtkBox to add ParaView specific API.
*/
* vtkPVBox extends vtkBox to add ParaView specific API. We add ability to
* provide a transform using position, scale and orientation. The transform can
* be applied to a unit box or a explicitly specified bounds.
*/
#ifndef vtkPVBox_h
#define vtkPVBox_h
......@@ -32,6 +34,32 @@ public:
vtkTypeMacro(vtkPVBox, vtkBox);
void PrintSelf(ostream& os, vtkIndent indent) override;
//@{
/**
* These bounds are used when `UseReferenceBounds` is set to true.
* In that case, the position, rotation and scale is assumed to be relative
* to these bounds. Otherwise, it's assumed to be absolute values i.e.
* relative to a unit box.
*/
void SetReferenceBounds(const double bds[6]);
void SetReferenceBounds(
double xmin, double xmax, double ymin, double ymax, double zmin, double zmax)
{
double bds[6] = { xmin, xmax, ymin, ymax, zmin, zmax };
this->SetReferenceBounds(bds);
}
vtkGetVector6Macro(ReferenceBounds, double);
//@}
//@{
/**
* Set to true to use ReferenceBounds as the basis for the transformation
* instead of unit box.
*/
void SetUseReferenceBounds(bool val);
vtkGetMacro(UseReferenceBounds, bool);
//@}
//@{
/**
* Get/Set Position of the box.
......@@ -79,6 +107,8 @@ protected:
double Position[3];
double Rotation[3];
double Scale[3];
double ReferenceBounds[6];
bool UseReferenceBounds;
private:
vtkPVBox(const vtkPVBox&) = delete;
......
......@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>245</width>
<height>244</height>
<width>230</width>
<height>329</height>
</rect>
</property>
<property name="windowTitle">
......@@ -17,7 +17,16 @@
<property name="spacing">
<number>2</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
......@@ -34,8 +43,17 @@
</widget>
</item>
<item>
<layout class="QGridLayout">
<property name="margin">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
......@@ -92,7 +110,108 @@
</layout>
</item>
<item>
<layout class="QGridLayout">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="referenceBoundsLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Reference Bounds&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="referenceBoundsHLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="useReferenceBounds">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use reference bounding box. When checked, &lt;span style=&quot; font-weight:600;&quot;&gt;Position&lt;/span&gt;, &lt;span style=&quot; font-weight:600;&quot;&gt;Rotation&lt;/span&gt;, and &lt;span style=&quot; font-weight:600;&quot;&gt;Scale&lt;/span&gt; are specified relative to the explicitly provided reference bounding box.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use Reference Bounds</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout2">
<item row="0" column="0">
<widget class="pqDoubleLineEdit" name="xmin">
<property name="toolTip">
<string>Minimum X</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="pqDoubleLineEdit" name="xmax">
<property name="toolTip">
<string>Maximum X</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="pqDoubleLineEdit" name="ymin">
<property name="toolTip">
<string>Minimum Y</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="pqDoubleLineEdit" name="ymax">
<property name="toolTip">
<string>Maximum Y</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="pqDoubleLineEdit" name="zmin">
<property name="toolTip">
<string>Minimum Z</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="pqDoubleLineEdit" name="zmax">
<property name="toolTip">
<string>Maximum Z</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Interactivity Controls&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout1">
<item row="3" column="0">
<widget class="QCheckBox" name="enableRotation">
<property name="toolTip">
......@@ -137,11 +256,27 @@
</item>
<item>
<widget class="QPushButton" name="resetBounds">
<property name="toolTip">
<string>Reset box using current data bounds</string>
</property>
<property name="text">
<string>Reset Bounds</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
......@@ -166,8 +301,112 @@
<tabstop>enableScaling</tabstop>
<tabstop>enableRotation</tabstop>
<tabstop>enableMoveFaces</tabstop>
<tabstop>useReferenceBounds</tabstop>
<tabstop>xmin</tabstop>
<tabstop>xmax</tabstop>
<tabstop>ymin</tabstop>
<tabstop>ymax</tabstop>
<tabstop>zmin</tabstop>
<tabstop>zmax</tabstop>
<tabstop>resetBounds</tabstop>
</tabstops>
<resources/>
<connections/>
<connections>
<connection>
<sender>useReferenceBounds</sender>
<signal>toggled(bool)</signal>
<receiver>xmin</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>114</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>56</x>
<y>157</y>
</hint>
</hints>
</connection>
<connection>
<sender>useReferenceBounds</sender>
<signal>toggled(bool)</signal>
<receiver>xmax</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>114</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>173</x>
<y>157</y>
</hint>
</hints>
</connection>
<connection>
<sender>useReferenceBounds</sender>
<signal>toggled(bool)</signal>
<receiver>ymin</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>114</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>56</x>
<y>186</y>
</hint>
</hints>
</connection>
<connection>
<sender>useReferenceBounds</sender>
<signal>toggled(bool)</signal>
<receiver>ymax</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>114</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>173</x>
<y>186</y>
</hint>
</hints>
</connection>
<connection>
<sender>useReferenceBounds</sender>
<signal>toggled(bool)</signal>
<receiver>zmax</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>114</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>173</x>
<y>215</y>
</hint>
</hints>
</connection>
<connection>
<sender>useReferenceBounds</sender>
<signal>toggled(bool)</signal>
<receiver>zmin</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>114</x>
<y>132</y>
</hint>
<hint type="destinationlabel">
<x>56</x>
<y>215</y>
</hint>
</hints>
</connection>
</connections>
</ui>
......@@ -32,14 +32,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "pqBoxPropertyWidget.h"
#include "ui_pqBoxPropertyWidget.h"
#include "pqUndoStack.h"
#include "vtkSMNewWidgetRepresentationProxy.h"
#include "vtkSMPropertyGroup.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMUncheckedPropertyHelper.h"
//-----------------------------------------------------------------------------
pqBoxPropertyWidget::pqBoxPropertyWidget(
vtkSMProxy* smproxy, vtkSMPropertyGroup* smgroup, QWidget* parentObject)
: Superclass("representations", "BoxWidgetRepresentation", smproxy, smgroup, parentObject)
, BoxIsRelativeToInput(false)
{
Ui::BoxPropertyWidget ui;
ui.setupUi(this);
......@@ -138,6 +140,36 @@ pqBoxPropertyWidget::pqBoxPropertyWidget(
ui.enableMoveFaces->hide();
}
auto useRefBounds = smgroup->GetProperty("UseReferenceBounds");
auto refBounds = smgroup->GetProperty("PlaceWidget");
if (useRefBounds && refBounds)
{
this->addPropertyLink(ui.useReferenceBounds, "checked", SIGNAL(toggled(bool)), useRefBounds);
this->addPropertyLink(ui.xmin, "text2", SIGNAL(textChangedAndEditingFinished()), refBounds, 0);
this->addPropertyLink(ui.xmax, "text2", SIGNAL(textChangedAndEditingFinished()), refBounds, 1);
this->addPropertyLink(ui.ymin, "text2", SIGNAL(textChangedAndEditingFinished()), refBounds, 2);
this->addPropertyLink(ui.ymax, "text2", SIGNAL(textChangedAndEditingFinished()), refBounds, 3);
this->addPropertyLink(ui.zmin, "text2", SIGNAL(textChangedAndEditingFinished()), refBounds, 4);
this->addPropertyLink(ui.zmax, "text2", SIGNAL(textChangedAndEditingFinished()), refBounds, 5);
}
else
{
ui.referenceBoundsLabel->hide();
ui.referenceBoundsHLine->hide();
ui.useReferenceBounds->hide();
ui.xmin->hide();
ui.xmax->hide();
ui.ymin->hide();
ui.ymax->hide();
ui.zmin->hide();
ui.zmax->hide();
// if `PlaceWidget` or `UseReferenceBounds` is not present, which is the
// case for `Transform`, the box is providing params relative to the input
// bounds.
this->BoxIsRelativeToInput = true;
}
this->connect(&this->WidgetLinks, SIGNAL(qtWidgetChanged()), SLOT(render()));
// link show3DWidget checkbox
......@@ -145,9 +177,46 @@ pqBoxPropertyWidget::pqBoxPropertyWidget(
ui.show3DWidget->connect(this, SIGNAL(widgetVisibilityToggled(bool)), SLOT(setChecked(bool)));
this->setWidgetVisible(ui.show3DWidget->isChecked());
// hiding this since this is not connected to anything currently. Need to
// figure out what exactly should it do.
ui.resetBounds->hide();
QObject::connect(ui.resetBounds, &QAbstractButton::clicked, [this, wdgProxy, useRefBounds](bool) {
auto bbox = this->dataBounds();
if (!bbox.IsValid())
{
return;
}
if (this->BoxIsRelativeToInput ||
(useRefBounds && vtkSMUncheckedPropertyHelper(useRefBounds).GetAsInt() == 1))
{
double bds[6];
bbox.GetBounds(bds);
vtkSMPropertyHelper(wdgProxy, "PlaceWidget").Set(bds, 6);
const double scale[3] = { 1, 1, 1 };
vtkSMPropertyHelper(wdgProxy, "Scale").Set(scale, 3);
const double pos[3] = { 0, 0, 0 };
vtkSMPropertyHelper(wdgProxy, "Position").Set(pos, 3);
const double orient[3] = { 0, 0, 0 };
vtkSMPropertyHelper(wdgProxy, "Rotation").Set(orient, 3);
}
else
{
double bds[6] = { 0, 1, 0, 1, 0, 1 };
vtkSMPropertyHelper(wdgProxy, "PlaceWidget").Set(bds, 6);
double lengths[3];
bbox.GetLengths(lengths);
vtkSMPropertyHelper(wdgProxy, "Scale").Set(lengths, 3);
const double orient[3] = { 0, 0, 0 };
vtkSMPropertyHelper(wdgProxy, "Rotation").Set(orient, 3);
vtkSMPropertyHelper(wdgProxy, "Position").Set(bbox.GetMinPoint(), 3);
}
wdgProxy->UpdateVTKObjects();
emit this->changeAvailable();
this->render();
});
}
//-----------------------------------------------------------------------------
......@@ -158,23 +227,17 @@ pqBoxPropertyWidget::~pqBoxPropertyWidget()
//-----------------------------------------------------------------------------
void pqBoxPropertyWidget::placeWidget()
{
vtkBoundingBox bbox = this->dataBounds();
if (!bbox.IsValid())
if (this->BoxIsRelativeToInput)
{
bbox = vtkBoundingBox(0, 1, 0, 1, 0, 1);
auto bbox = this->dataBounds();
if (bbox.IsValid())