X
38 Rate this article:
No rating

INTERNAL/REVIEW: Resetting Orientation In iVolume Via Programming An iTools Transform Matrix

Zachary Norman

[Needs to be reviewed for Compliance and IP issues (i.e. .pro file included)]


This Help Article demonstrates implementing a new 'Reset' menu button in iVolume, which allows users to set a volume visualization to the position, orientation and scale it had when it originally loaded on the iTools window. The movement of the graphics object is implemented in a way that makes it user-undo-/redoable in the iTools 'Edit' menu. Readers can extrapolate from this example to manipulate other Visualization Layer objects than just IDLitVisVolume, e.g. IDLitVisPlot, IDLitVisSurface, IDLitVisContour, and to perform 2D as well as 3D movement.

This Help Article also provides another example of how to add a new operation to a basic iTools interface. General knowledge of how to do this might be critical to understanding the algorithm discussed below. The chapter
Part II: Using the iTools Component Framework -> Creating an Operation in the iTool Developer's Guide (itooldevguide.pdf) is available for that training, as is our Help Article #3528 ("Creating an iTool with a custom operation").

The 'Example Code' below (and corresponding downloadables) implements a program that brings up IDL's 'head.dat' volume data example in a standard IVOLUME interface with just one modification: the addition of an 'Operations->Volume->Reset Orientation' button. You can run this program by downloading the two linked files to somewhere in your IDL search path, then typing

    .run EX_IVOLUME_WITH_RESET_OP

at the IDL command line. You can observe the new 'Reset Orientation' button's functionality by translating, rotating and/or scaling the visualization around on the iTools window, then clicking the new button.

The reset movement/transformation algorithm is detailed most thoroughly in the comments of the 'Example Code' for the custom
IDLitOpResetOrientation function, shown in the next section of this tech tip. Here, however, are the basics of this algorithm:

  1. Implement a generic structure definition and 'Init' method for the new operation, based on the most basic inheritance from iTools' 'IDLitOperation' class. One needs to customize only the 'DoAction' method of this custom Operation class.

  2. After getting the Volume visualization object(s) use the 'IDLitOperation::RecordInitialValues' method to record the current state of the iTools visualization, so that the use can 'Edit->Undo' or 'Edit->Redo' to that state, if he/she desires.

  3. Before you reset, save in a temp variable the graphics cumulative transform matrix stored in the TRANSFORM property of the Volume visualization object(s). The cumulative transform matrix provides the math formula for mapping the volume data coordinates to their current location in the iTools view window.

  4. Reset the transform matrix to pure identity, then re-Rotate the volume object. To find out the exact rotation values for a routine iTools initial orientation , we copied code from the 'IDLitVisDataSpace__define.pro' file. Lines in the 'OnDataRangeChange' method there provided the algorithm that creates a default orientation for all 3D objects in iTools. The critical lines are:

      oTarget->IDLgrModel::Rotate, [1, 0, 0], -90
      oTarget->IDLgrModel::Rotate, [0, 1, 0], 30
      oTarget->IDLgrModel::Rotate, [1, 0, 0], 30

    In pasting these into the IDLitOpResetOrientation::DoAction method it became clear that these could be executed on the simple dataspace returned by a call to the iVolume's inherited 'GetDataspace()' method.

    By themselves the above lines are not sufficient; they will namely just Rotate relative to a current position. So, right before those lines execute, we need to restore the basic identity graphics matrix to the model with a call to
    IDLgrModel::Reset.

  5. At this point, the above re-positioning calls are not recorded in a way that is Undo-/Redoable in the iTools menu. To enable proper 'Edit->Undo/Redo' behavior, we get the new TRANSFORM property of the reset volume object(s). We will feed this to a subsequent 'DoSetProperty' call, which will make it Undo-/Redoable. However, to make sure that the initial pre-Reset state is also Undo-/Redoable, its state must be seen by 'DoSetProperty' as the current state at the time of the 'DoSetProperty' call. Thus, the new TRANSFORM matrix is also stored in a temp variable, while the old one is re-assigned as the current TRANSFORM property of the Volume visualization object..

  6. Finally, a 'DoSetProperty' call (followed by its companion 'CommitActions' call) executes and records the "official" switch from the original orientation to the iTools basic reset orientation.

The comments in the code below should help explain all this still more. That example code is also downloadable from the following links: idlitopresetorientation__define.pro and ex_ivolume_with_reset_op.pro.Solution:

; File: idlitopresetorientation__define.pro
; Purpose: This is a complete class definition file for a custom iTools
; operation that, in its current form, is specifically designed for IVOLUME.
; Users who execute this operation after translating, rotating or scaling
; a volume object on the iTools window, will find their volume object
; restored to the display state it started with at original data loading.
; This operation is specifically implemented to be 'Edit->Undo'able and
; 'Edit->Redo'able.

; 'DoAction' is the method that is triggered when an
;     oTool->DoAction('/TOOLS/VOLUME TOOL/OPERATIONS/OPERATIONS/VOLUME/RESET ORIENTATION')
; call is triggered.
FUNCTION IDLitOpResetOrientation::DoAction, oTool
; Make sure we have a valid iTool object.
if ~obj_valid(oTool) then return, obj_new()

; Get the selected objects. Select only IDLitVisVolume objects. If
; there are no surface objects selected, return a null object.
oTargets = oTool->GetSelectedItems()
volumes = obj_new()
for i = 0, n_elements(oTargets)-1 do begin
    if (obj_isa(oTargets[i], 'IDLitVisualization')) then begin
       volumes = obj_valid(volumes) ? [volumes, oTargets[i]] : oTargets[i]
    endif
endfor

; Record the current state of the visualization, so that it can
; be undoable/redoable.
if (~obj_valid(volumes)) then return, obj_new()
; Create a command set:
oCmdSet = self->IDLitOperation::DoAction(oTool)
; Record the initial values
if (~self->RecordInitialValues(oCmdSet, volumes, '')) then begin
    obj_destroy, oCmdSet
    return, obj_new()
endif

; Do the real work of this operation. Reset the graphics transform matrix
; to basic identity, then run the Rotation that IDLitVisDataSpace does when
; its data range is first initialized.
for i = 0, n_elements(volumes)-1 do begin
    oDataSpace = volumes[i]->GetDataSpace()
    oTarget = oDataSpace->GetManipulatorTarget()
    ; Save the old transform so we can restore it right before a
    ; 'DoSetProperty' call that will allow iTools to 'Undo' the
    ; 'Reset' operation and get back to the last orientation
    ; before the 'Reset'
    oTarget->GetProperty, TRANSFORM=preResetOrientationTransform
    ; For the initial 3D view, iTools rotates to this "nice" viewing angle
    oTarget->Reset   ; converts Transform to its identity matrix origin
    if (oDataSpace->Is3D()) then begin
        oTarget->IDLgrModel::Rotate, [1, 0, 0], -90
        oTarget->IDLgrModel::Rotate, [0, 1, 0], 30
        oTarget->IDLgrModel::Rotate, [1, 0, 0], 30
    endif
    ; Get the new transform matrix for the upcoming call to 
    ; 'DoSetProperty'. Then set back the Volume object's TRANSFORM
    ; property to its pre-Reset state, so that the upcoming
    ; 'DoSetProperty' call can be properly undone by 'Edit->Undo',
    ; if the user wants.
    oTarget->GetProperty, TRANSFORM=resetTransform
    oTarget->SetProperty, TRANSFORM=preResetOrientationTransform
    ; Now use DoSetProperty to get Undo/Redo.
    success = oTool->DoSetProperty(oTarget->GetFullIdentifier(), $
        'TRANSFORM', resetTransform)
endfor
; Commit our Undo/Redo actions.
oTool->CommitActions
oTool->RefreshCurrentWindow
; Return a null object since we've already handled undo/redo.
return, obj_new()
END


; Generic Init function
FUNCTION IDLitOpResetOrientation::Init, _REF_EXTRA=_extra
if (self->IDLitOperation::Init(_EXTRA=_extra) eq 0) $
    then return, 0
return, 1
END


; Generic IDLitOperation structure definition
PRO IDLitOpResetOrientation__define
struct = { IDLitOpResetOrientation, $
    INHERITS IDLitOperation $
    }
END



; File: ex_ivolume_with_reset_op.pro
; Syntax: .run EX_IVOLUME_WITH_RESET_OP
; Purpose: A wrapper function that implements a standard iVolume interface
; with one extra button, 'Operations->Volume->Reset Orientation', to execute
; the above IDLitOperation code.

; Open IDL's example 'head.dat' 3D dataset file and display in IVOLUME
file = FILEPATH('head.dat', SUBDIRECTORY = ['examples', 'data'])
data = READ_BINARY(file, DATA_DIMS = [80, 100, 57])
IVOLUME, data

; Add a button to the above standard IVOLUME GUI, which triggers methods
; in the custom IDLitOpResetOrientation class.
idTool = itGetCurrent(TOOL=oTool)
oTool->RegisterOperation, 'Reset Orientation', $
	'IDLitOpResetOrientation', $
	IDENTIFIER='OPERATIONS/VOLUME/RESET ORIENTATION', $
	DESCRIPTION='Reset to the original orientation'
	
; The below is needed to get the dataset to render (i.e. display) when
; this wrapper procedure starts IVOLUME. You may want to uncheck the
; 'Auto-Render' checkbox after the visualization loads; the visualization
; can be slow to update when this property is checked. If you do uncheck,
; you have to press the 'Render' button every time you want to review
; the volume object. 
idVisVolume = oTool->FindIdentifiers('*VOLUME', /VISUALIZATIONS)
oVisVolume = oTool->GetByIdentifier(idVisVolume)
print, oTool->DoSetProperty(idVisVolume, 'AUTO_RENDER', 1)
oVisVolume->RenderVolume
	
END