/*========================================================================= Program: GDCM (Grassroots DICOM). A DICOM library Copyright (c) 2006-2011 Mathieu Malaterre All rights reserved. See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "gdcmSurfaceReader.h" #include "gdcmMediaStorage.h" #include "gdcmAttribute.h" #include "gdcmString.h" namespace gdcm { SurfaceReader::SurfaceReader() = default; SurfaceReader::~SurfaceReader() = default; unsigned long SurfaceReader::GetNumberOfSurfaces() const { return (unsigned long)Segments.size(); } bool SurfaceReader::Read() { bool res = false; if (!SegmentReader::Read()) { return res; } const FileMetaInformation & header = F->GetHeader(); MediaStorage ms = header.GetMediaStorage(); if( ms == MediaStorage::SurfaceSegmentationStorage ) { res = ReadSurfaces(); } else { // Try to find Surface Sequence const DataSet & dsRoot = F->GetDataSet(); if (dsRoot.FindDataElement( Tag(0x0066, 0x0002) )) { res = ReadSurfaces(); } } return res; } bool SurfaceReader::ReadSurfaces() { bool res = false; const DataSet & ds = F->GetDataSet(); // Surface Sequence const Tag surfaceSQTag(0x0066, 0x0002); if (ds.FindDataElement(surfaceSQTag)) { SmartPointer< SequenceOfItems > surfaceSQ = ds.GetDataElement(surfaceSQTag).GetValueAsSQ(); if ( surfaceSQ->GetNumberOfItems() == 0) { gdcmErrorMacro( "No surface found" ); return false; } SequenceOfItems::ConstIterator itSurface = surfaceSQ->Begin(); SequenceOfItems::ConstIterator itEndSurface = surfaceSQ->End(); unsigned long idxItem = 1; for (; itSurface != itEndSurface; itSurface++) { if ( !ReadSurface( *itSurface, idxItem ) ) { gdcmWarningMacro( "Surface "< surface = new Surface; const DataSet & surfacesDS = surfaceItem.GetNestedDataSet(); // Recommended Display Grayscale Value Attribute<0x0062, 0x000C> recommendedDisplayGrayscaleValue; recommendedDisplayGrayscaleValue.SetFromDataSet( surfacesDS ); surface->SetRecommendedDisplayGrayscaleValue( recommendedDisplayGrayscaleValue.GetValue() ); // Recommended Display CIELab Value Attribute<0x0062, 0x000D> recommendedDisplayCIELabValue; recommendedDisplayCIELabValue.SetFromDataSet( surfacesDS ); const unsigned short * array = recommendedDisplayCIELabValue.GetValues(); unsigned short CIELavValue[3] = {0, 0, 0}; unsigned int i = 0; while (array != nullptr && i < 3) CIELavValue[i++] = *(array++); surface->SetRecommendedDisplayCIELabValue( CIELavValue ); // Surface Number Attribute<0x0066, 0x0003> surfaceNumberAt; surfaceNumberAt.SetFromDataSet( surfacesDS ); unsigned long surfaceNumber = idx; if ( !surfaceNumberAt.GetAsDataElement().IsEmpty() ) { surfaceNumber = surfaceNumberAt.GetValue(); } surface->SetSurfaceNumber( surfaceNumber ); // Surface Comments Attribute<0x0066, 0x0004> surfaceComments; surfaceComments.SetFromDataSet( surfacesDS ); surface->SetSurfaceComments( surfaceComments.GetValue() ); // Surface Processing Attribute<0x0066, 0x0009> surfaceProcessingAt; surfaceProcessingAt.SetFromDataSet( surfacesDS ); String<> surfaceProcessingStr( surfaceProcessingAt.GetValue() ); bool surfaceProcessing; if (surfaceProcessingStr.Trim() == "YES") surfaceProcessing = true; else surfaceProcessing = false; surface->SetSurfaceProcessing( surfaceProcessing ); if (surfaceProcessing) { // Surface Processing Ratio Attribute<0x0066, 0x000A> surfaceProcessingRatioAt; surfaceProcessingRatioAt.SetFromDataSet( surfacesDS ); surface->SetSurfaceProcessingRatio( surfaceProcessingRatioAt.GetValue() ); // Surface Processing Description Attribute<0x0066, 0x000B> surfaceProcessingDescriptionAt; surfaceProcessingDescriptionAt.SetFromDataSet( surfacesDS ); surface->SetSurfaceProcessingDescription( surfaceProcessingDescriptionAt.GetValue() ); //***** Surface Processing Algorithm Identification Sequence *****// if( surfacesDS.FindDataElement( Tag(0x0066, 0x0035) ) ) { SmartPointer processingAlgoSQ = surfacesDS.GetDataElement( Tag(0x0066, 0x0035) ).GetValueAsSQ(); if (processingAlgoSQ->GetNumberOfItems() > 0) // Only one item (type 1) { const Item & processingAlgoItem = processingAlgoSQ->GetItem(1); const DataSet & processingAlgoDS = processingAlgoItem.GetNestedDataSet(); //***** Algorithm Family Code Sequence *****// if( processingAlgoDS.FindDataElement( Tag(0x0066, 0x002F) ) ) { SmartPointer algoFamilySQ = processingAlgoDS.GetDataElement( Tag(0x0066, 0x002F) ).GetValueAsSQ(); if (algoFamilySQ->GetNumberOfItems() > 0) // Only one item (type 1) { const Item & algoFamilyItem = algoFamilySQ->GetItem(1); const DataSet & algoFamilyDS = algoFamilyItem.GetNestedDataSet(); //***** CODE SEQUENCE MACRO ATTRIBUTES *****// SegmentHelper::BasicCodedEntry & processingAlgo = surface->GetProcessingAlgorithm(); // Code Value (Type 1) Attribute<0x0008, 0x0100> codeValueAt; codeValueAt.SetFromDataSet( algoFamilyDS ); processingAlgo.CV = codeValueAt.GetValue(); // Coding Scheme (Type 1) Attribute<0x0008, 0x0102> codingSchemeAt; codingSchemeAt.SetFromDataSet( algoFamilyDS ); processingAlgo.CSD = codingSchemeAt.GetValue(); // Code Meaning (Type 1) Attribute<0x0008, 0x0104> codeMeaningAt; codeMeaningAt.SetFromDataSet( algoFamilyDS ); processingAlgo.CM = codeMeaningAt.GetValue(); } } } } } // Recommended Presentation Opacity Attribute<0x0066, 0x000C> recommendedPresentationOpacity; recommendedPresentationOpacity.SetFromDataSet( surfacesDS ); surface->SetRecommendedPresentationOpacity( recommendedPresentationOpacity.GetValue() ); // Recommended Presentation Type Attribute<0x0066, 0x000D> recommendedPresentationType; recommendedPresentationType.SetFromDataSet( surfacesDS ); surface->SetRecommendedPresentationType( Surface::GetVIEWType( recommendedPresentationType.GetValue() ) ); // Finite Volume Attribute<0x0066, 0x000E> finiteVolumeAt; finiteVolumeAt.SetFromDataSet( surfacesDS ); Surface::STATES finiteVolume = Surface::GetSTATES( finiteVolumeAt.GetValue() ); if ( finiteVolume == Surface::STATES_END) finiteVolume = Surface::UNKNOWN; surface->SetFiniteVolume( finiteVolume ); // Manifold Attribute<0x0066, 0x0010> manifoldAt; manifoldAt.SetFromDataSet( surfacesDS ); Surface::STATES manifold = Surface::GetSTATES( manifoldAt.GetValue() ); if ( manifold == Surface::STATES_END ) manifold = Surface::UNKNOWN; surface->SetManifold( manifold ); //***** Surface Points Sequence ******// if ( !ReadPointMacro(surface, surfacesDS) ) return false; //***** Surface Points Normals Sequence ******// const Tag surfaceNormalsSQTag(0x0066, 0x0012); if ( surfacesDS.FindDataElement(surfaceNormalsSQTag)) { SmartPointer< SequenceOfItems > surfaceNormalsSQ = surfacesDS.GetDataElement(surfaceNormalsSQTag).GetValueAsSQ(); if ( surfaceNormalsSQ->GetNumberOfItems() > 0) // One Item shall be permitted { const DataSet & surfaceNormalsDS = surfaceNormalsSQ->GetItem(1).GetNestedDataSet(); // Number of Vectors Attribute<0x0066, 0x001E> numberOfVectors; numberOfVectors.SetFromDataSet( surfaceNormalsDS ); surface->SetNumberOfVectors( numberOfVectors.GetValue() ); // Vector Dimensionality Attribute<0x0066, 0x001F> vectorDimensionality; vectorDimensionality.SetFromDataSet( surfaceNormalsDS ); surface->SetVectorDimensionality( vectorDimensionality.GetValue() ); // Vector Accuracy (Type 3) const Tag vectorAccuracyTag = Tag(0x0066, 0x0020); if ( surfaceNormalsDS.FindDataElement( vectorAccuracyTag ) ) { const DataElement & vectorAccuracyDE = surfaceNormalsDS.GetDataElement( vectorAccuracyTag ); if ( !vectorAccuracyDE.IsEmpty() ) { Attribute<0x0066, 0x0020> vectorAccuracyAt; vectorAccuracyAt.SetFromDataElement( vectorAccuracyDE ); surface->SetVectorAccuracy( vectorAccuracyAt.GetValues() ); } } const Tag vectorCoordDataTag = Tag(0x0066, 0x0021); if( surfaceNormalsDS.FindDataElement( vectorCoordDataTag ) ) { const DataElement & de = surfaceNormalsDS.GetDataElement( vectorCoordDataTag ); surface->SetVectorCoordinateData( de ); } else { gdcmWarningMacro( "No Vector Coordinate Data Found" ); return false; } } else { gdcmWarningMacro( "Surface Point Normals Sequence empty" ); // return false; } } //***** Surface Mesh Primitives Sequence ******// const Tag surfacePrimitivesSQTag(0x0066, 0x0013); if ( !surfacesDS.FindDataElement(surfacePrimitivesSQTag)) { gdcmWarningMacro( "No Surface Mesh Primitives Sequence Found" ); return false; } SmartPointer< SequenceOfItems > surfacePrimitivesSQ = surfacesDS.GetDataElement(surfacePrimitivesSQTag).GetValueAsSQ(); if ( surfacePrimitivesSQ->GetNumberOfItems() < 1) // One Item shall be permitted { gdcmWarningMacro( "Surface Mesh Primitives Sequence empty" ); return false; } SmartPointer< MeshPrimitive > meshPrimitive = new MeshPrimitive; DataSet & surfacePrimitivesDS = surfacePrimitivesSQ->GetItem(1).GetNestedDataSet(); Tag typedTag; // Long Triangle Point Index List if (surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0041) )) { typedTag = Tag(0x0066, 0x0041); meshPrimitive->SetPrimitiveType( MeshPrimitive::TRIANGLE ); } // Long Edge Point Index List else if (surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0042)) ) { typedTag = Tag(0x0066, 0x0042); meshPrimitive->SetPrimitiveType( MeshPrimitive::EDGE ); } // Long Vertex Point Index List else if (surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0043)) ) { typedTag = Tag(0x0066, 0x0043); meshPrimitive->SetPrimitiveType( MeshPrimitive::VERTEX ); } // RETIRED Triangle Point Index List else if (surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0023) )) { typedTag = Tag(0x0066, 0x0023); meshPrimitive->SetPrimitiveType( MeshPrimitive::TRIANGLE ); gdcmErrorMacro("Retrieving primitive data from retired tag 'Triangle Point Index List' (0066,0023)"); } // RETIRED Edge Point Index List else if (surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0024)) ) { typedTag = Tag(0x0066, 0x0024); meshPrimitive->SetPrimitiveType( MeshPrimitive::EDGE ); gdcmErrorMacro("Retrieving primitive data from retired tag 'Edge Point Index List' (0066,0024)"); } // RETIRED Vertex Point Index List else if (surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0025)) ) { typedTag = Tag(0x0066, 0x0025); meshPrimitive->SetPrimitiveType( MeshPrimitive::VERTEX ); gdcmErrorMacro("Retrieving primitive data from retired tag 'Vertex Point Index List' (0066,0025)"); } else { SmartPointer< SequenceOfItems > typedSQ; if (surfacePrimitivesDS.FindDataElement(Tag(0x0066, 0x0026))) { SmartPointer< SequenceOfItems > sequence = surfacePrimitivesDS.GetDataElement( Tag(0x0066, 0x0026) ).GetValueAsSQ(); if(sequence->GetNumberOfItems() > 0) { typedSQ = sequence; meshPrimitive->SetPrimitiveType( MeshPrimitive::TRIANGLE_STRIP ); } } if(!typedSQ && surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0027)) ) { SmartPointer< SequenceOfItems > sequence = surfacePrimitivesDS.GetDataElement( Tag(0x0066, 0x0027) ).GetValueAsSQ(); if(sequence->GetNumberOfItems() > 0) { typedSQ = sequence; meshPrimitive->SetPrimitiveType( MeshPrimitive::TRIANGLE_FAN ); } } if (!typedSQ && surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0028)) ) { SmartPointer< SequenceOfItems > sequence = surfacePrimitivesDS.GetDataElement( Tag(0x0066, 0x0028) ).GetValueAsSQ(); if(sequence->GetNumberOfItems() > 0) { typedSQ = sequence; meshPrimitive->SetPrimitiveType( MeshPrimitive::LINE ); } } if (!typedSQ && surfacePrimitivesDS.FindDataElement( Tag(0x0066, 0x0034)) ) { SmartPointer< SequenceOfItems > sequence = surfacePrimitivesDS.GetDataElement( Tag(0x0066, 0x0034) ).GetValueAsSQ(); if(sequence->GetNumberOfItems() > 0) { typedSQ = sequence; meshPrimitive->SetPrimitiveType( MeshPrimitive::FACET ); } } if(!typedSQ) { gdcmErrorMacro( "Unknown surface mesh primitives type" ); return false; } if (typedSQ->GetNumberOfItems() > 0) { const size_t nbItems = typedSQ->GetNumberOfItems(); MeshPrimitive::PrimitivesData & primitivesData= meshPrimitive->GetPrimitivesData(); primitivesData.reserve( nbItems ); SequenceOfItems::ConstIterator it = typedSQ->Begin(); SequenceOfItems::ConstIterator itEnd= typedSQ->End(); for (; it != itEnd; it++) { const DataSet & typedPrimitivesDS = it->GetNestedDataSet(); // Primitive Sequence if ( typedPrimitivesDS.FindDataElement( Tag(0x0066, 0x0040)) ) { meshPrimitive->AddPrimitiveData( typedPrimitivesDS.GetDataElement( Tag(0x0066, 0x0040)) ); } // RETIRED Primitive Sequence else if ( typedPrimitivesDS.FindDataElement( Tag(0x0066, 0x0029)) ) { meshPrimitive->AddPrimitiveData( typedPrimitivesDS.GetDataElement( Tag(0x0066, 0x0029)) ); gdcmErrorMacro("Retrieving primitive data from retired tag 'Primitive Point Index List' (0066,0029)"); } else { gdcmWarningMacro( "Missing Primitive Point Index List" ); return false; } } } else { gdcmWarningMacro( "Mesh Primitive typed Sequence empty" ); return false; } } if (typedTag.GetElementTag() != 0) { const DataElement & meshPrimitiveData = surfacePrimitivesDS.GetDataElement( typedTag ); meshPrimitive->SetPrimitiveData( meshPrimitiveData ); } else { gdcmWarningMacro( "No typed Point Index List found" ); return false; } // Get the appropriated segment SmartPointer< Segment > segment = Segments[surfaceNumber]; //***** Segment Sequence *****// SmartPointer segmentsSQ = F->GetDataSet().GetDataElement( Tag(0x0062, 0x0002) ).GetValueAsSQ(); SequenceOfItems::ConstIterator itSegment = segmentsSQ->Begin(); SequenceOfItems::ConstIterator itEndSegment = segmentsSQ->End(); bool findItem = false; while( !findItem && itSegment != itEndSegment ) { const DataSet & segmentDS = itSegment->GetNestedDataSet(); //***** Referenced Surface Sequence *****// SmartPointer refSurfaceSQ = segmentDS.GetDataElement( Tag(0x0066, 0x002B) ).GetValueAsSQ(); SequenceOfItems::ConstIterator itRefSurface = refSurfaceSQ->Begin(); SequenceOfItems::ConstIterator itEndRefSurface = refSurfaceSQ->End(); while( !findItem && itRefSurface != itEndRefSurface ) { const DataSet & refSurfaceDS = itRefSurface->GetNestedDataSet(); // Referenced Surface Number Attribute<0x0066, 0x002C> refSurfaceNumberAt; refSurfaceNumberAt.SetFromDataSet( refSurfaceDS ); unsigned long refSurfaceNumber; if ( !refSurfaceNumberAt.GetAsDataElement().IsEmpty() ) { refSurfaceNumber = refSurfaceNumberAt.GetValue(); } else { refSurfaceNumber = idx; } if (refSurfaceNumber == surfaceNumber) { findItem = true; //***** Segment Surface Generation Algorithm Identification Sequence *****// if( refSurfaceDS.FindDataElement( Tag(0x0066, 0x002D) ) ) { SmartPointer algoSQ = refSurfaceDS.GetDataElement( Tag(0x0066, 0x002D) ).GetValueAsSQ(); if (algoSQ->GetNumberOfItems() > 0) // Only one item is a type 1 { const Item & algoItem = algoSQ->GetItem(1); const DataSet & algoDS = algoItem.GetNestedDataSet(); //***** Algorithm Family Code Sequence *****// if( algoDS.FindDataElement( Tag(0x0066, 0x002F) ) ) { SmartPointer algoFamilySQ = algoDS.GetDataElement( Tag(0x0066, 0x002F) ).GetValueAsSQ(); if (algoFamilySQ->GetNumberOfItems() > 0) // Only one item is a type 1 { const Item & algoFamilyItem = algoFamilySQ->GetItem(1); const DataSet & algoFamilyDS = algoFamilyItem.GetNestedDataSet(); //***** CODE SEQUENCE MACRO ATTRIBUTES *****// SegmentHelper::BasicCodedEntry & algoFamily = surface->GetAlgorithmFamily(); // Code Value (Type 1) Attribute<0x0008, 0x0100> codeValueAt; codeValueAt.SetFromDataSet( algoFamilyDS ); algoFamily.CV = codeValueAt.GetValue(); // Coding Scheme (Type 1) Attribute<0x0008, 0x0102> codingSchemeAt; codingSchemeAt.SetFromDataSet( algoFamilyDS ); algoFamily.CSD = codingSchemeAt.GetValue(); // Code Meaning (Type 1) Attribute<0x0008, 0x0104> codeMeaningAt; codeMeaningAt.SetFromDataSet( algoFamilyDS ); algoFamily.CM = codeMeaningAt.GetValue(); } } // Algorithm Version Attribute<0x0066, 0x0031> algoVersionAt; algoVersionAt.SetFromDataSet( algoDS ); surface->SetAlgorithmVersion( algoVersionAt.GetValue() ); // Algorithm Name Attribute<0x0066, 0x0036> algoNameAt; algoNameAt.SetFromDataSet( algoDS ); surface->SetAlgorithmName( algoNameAt.GetValue() ); } } // else assert? return false? gdcmWarning? } itRefSurface++; } itSegment++; } // Add a MeshPrimitive to the surface surface->SetMeshPrimitive( *meshPrimitive ); // Add surface to the appropriated segment segment->AddSurface(surface); return true; } bool SurfaceReader::ReadPointMacro(SmartPointer< Surface > surface, const DataSet & surfaceDS) { //***** Surface Points Sequence ******// const Tag surfacePointsSQTag(0x0066, 0x0011); if ( !surfaceDS.FindDataElement(surfacePointsSQTag)) { gdcmWarningMacro( "No Surface Point Sequence Found" ); return false; } SmartPointer< SequenceOfItems > surfacePointsSQ = surfaceDS.GetDataElement(surfacePointsSQTag).GetValueAsSQ(); if ( surfacePointsSQ->GetNumberOfItems() == 0) // One Item shall be permitted { gdcmWarningMacro( "Surface Point Sequence empty" ); return false; } const DataSet & surfacePointsDS = surfacePointsSQ->GetItem(1).GetNestedDataSet(); const Tag pointCoordDataTag = Tag(0x0066, 0x0016); if( !surfacePointsDS.FindDataElement( pointCoordDataTag ) ) { gdcmWarningMacro( "No Point Coordinates Data Found" ); return false; } const DataElement & pointCoordDataDe = surfacePointsDS.GetDataElement( pointCoordDataTag ); surface->SetPointCoordinatesData( pointCoordDataDe ); // Number of Surface Points const Tag numberOfSurfacePointsTag = Tag(0x0066, 0x0015); if (surfacePointsDS.FindDataElement( numberOfSurfacePointsTag ) && !surfacePointsDS.GetDataElement( numberOfSurfacePointsTag ).IsEmpty() ) { Attribute<0x0066, 0x0015> numberOfSurfacePointsAt; numberOfSurfacePointsAt.SetFromDataSet( surfacePointsDS ); surface->SetNumberOfSurfacePoints( numberOfSurfacePointsAt.GetValue() ); } else { const unsigned long numberOfSurfacePoints = (unsigned long) ( pointCoordDataDe.GetVL().GetLength() / (VR::GetLength(VR::OF) * 3) ); surface->SetNumberOfSurfacePoints( numberOfSurfacePoints ); } // Point Position Accuracy (Type 3) const Tag pointPositionAccuracyTag = Tag(0x0066, 0x0017); if (surfacePointsDS.FindDataElement( pointPositionAccuracyTag ) && !surfacePointsDS.GetDataElement( pointPositionAccuracyTag ).IsEmpty() ) { Attribute<0x0066, 0x0017> pointPositionAccuracyAt; pointPositionAccuracyAt.SetFromDataSet( surfacePointsDS ); surface->SetPointPositionAccuracy( pointPositionAccuracyAt.GetValues() ); } // Mean Point Distance (Type 3) const Tag meanPointDistanceTag = Tag(0x0066, 0x0018); if (surfacePointsDS.FindDataElement( meanPointDistanceTag ) && !surfacePointsDS.GetDataElement( meanPointDistanceTag ).IsEmpty() ) { Attribute<0x0066, 0x0018> meanPointDistanceAt; meanPointDistanceAt.SetFromDataSet( surfacePointsDS ); surface->SetMeanPointDistance( meanPointDistanceAt.GetValue() ); } // Maximum Point Distance (Type 3) const Tag maximumPointDistanceTag = Tag(0x0066, 0x0019); if (surfacePointsDS.FindDataElement( maximumPointDistanceTag ) && !surfacePointsDS.GetDataElement( maximumPointDistanceTag ).IsEmpty() ) { Attribute<0x0066, 0x0019> maximumPointDistanceAt; maximumPointDistanceAt.SetFromDataSet( surfacePointsDS ); surface->SetMaximumPointDistance( maximumPointDistanceAt.GetValue() ); } // Point Bounding Box Coordinates (Type 3) const Tag pointsBoundingBoxCoordinatesTag = Tag(0x0066, 0x001a); if (surfacePointsDS.FindDataElement( pointsBoundingBoxCoordinatesTag ) && !surfacePointsDS.GetDataElement( pointsBoundingBoxCoordinatesTag ).IsEmpty() ) { Attribute<0x0066, 0x001a> pointsBoundingBoxCoordinatesAt; pointsBoundingBoxCoordinatesAt.SetFromDataSet( surfacePointsDS ); surface->SetPointsBoundingBoxCoordinates( pointsBoundingBoxCoordinatesAt.GetValues() ); } // Axis of Rotation (Type 3) const Tag axisOfRotationTag = Tag(0x0066, 0x001b); if (surfacePointsDS.FindDataElement( axisOfRotationTag ) && !surfacePointsDS.GetDataElement( axisOfRotationTag ).IsEmpty() ) { Attribute<0x0066, 0x001b> axisOfRotationAt; axisOfRotationAt.SetFromDataSet( surfacePointsDS ); surface->SetAxisOfRotation( axisOfRotationAt.GetValues() ); } // Center of Rotation (Type 3) const Tag centerOfRotationTag = Tag(0x0066, 0x001c); if (surfacePointsDS.FindDataElement( centerOfRotationTag ) && !surfacePointsDS.GetDataElement( centerOfRotationTag ).IsEmpty() ) { Attribute<0x0066, 0x001c> centerOfRotationAt; centerOfRotationAt.SetFromDataSet( surfacePointsDS ); surface->SetAxisOfRotation( centerOfRotationAt.GetValues() ); } return true; } }