Skip to content

Commit

Permalink
Added undo command classes for point cloud editing
Browse files Browse the repository at this point in the history
  • Loading branch information
uclaros committed Jan 10, 2025
1 parent a2c3471 commit 3cb5582
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 94 deletions.
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,7 @@ set(QGIS_CORE_SRCS
pointcloud/qgspointcloudrendererregistry.cpp
pointcloud/qgspointcloudrgbrenderer.cpp
pointcloud/qgspointcloudlayerexporter.cpp
pointcloud/qgspointcloudlayerundocommand.cpp

pointcloud/expression/qgspointcloudexpression.cpp
pointcloud/expression/qgspointcloudexpressionnode.cpp
Expand Down Expand Up @@ -1742,6 +1743,7 @@ set(QGIS_CORE_HDRS
pointcloud/qgspointcloudrendererregistry.h
pointcloud/qgspointcloudrgbrenderer.h
pointcloud/qgspointcloudlayerexporter.h
pointcloud/qgspointcloudlayerundocommand.h

pointcloud/expression/qgspointcloudexpression.h
pointcloud/expression/qgspointcloudexpressionnode.h
Expand Down
52 changes: 41 additions & 11 deletions src/core/pointcloud/qgspointcloudlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "qgstaskmanager.h"
#include "qgsthreadingutils.h"
#include "qgspointcloudlayerprofilegenerator.h"
#include "qgspointcloudlayerundocommand.h"
#ifdef HAVE_COPC
#include "qgscopcpointcloudindex.h"
#endif
Expand Down Expand Up @@ -73,6 +74,8 @@ QgsPointCloudLayer::QgsPointCloudLayer( const QString &uri,

setLegend( QgsMapLayerLegend::defaultPointCloudLegend( this ) );
connect( this, &QgsPointCloudLayer::subsetStringChanged, this, &QgsMapLayer::configChanged );
connect( undoStack(), &QUndoStack::indexChanged, this, &QgsMapLayer::layerModified );
connect( this, &QgsMapLayer::layerModified, this, [ = ] { triggerRepaint(); } );
}

QgsPointCloudLayer::~QgsPointCloudLayer()
Expand Down Expand Up @@ -1026,11 +1029,7 @@ bool QgsPointCloudLayer::rollBack()
if ( !mEditIndex )
return false;

if ( isModified() )
{
emit layerModified();
triggerRepaint();
}
undoStack()->clear();

mEditIndex = QgsPointCloudIndex();
emit editingStopped();
Expand Down Expand Up @@ -1062,21 +1061,52 @@ bool QgsPointCloudLayer::isModified() const
return mEditIndex.isModified();
}

bool QgsPointCloudLayer::changeAttributeValue( const QgsPointCloudNodeId &n, const QVector<int> &pts, const QgsPointCloudAttribute &attribute, double value )
bool QgsPointCloudLayer::changeAttributeValue( const QgsPointCloudNodeId &n, const QVector<int> &points, const QgsPointCloudAttribute &attribute, double value )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mEditIndex )
return false;

QgsPointCloudLayerEditUtils utils( this );
// Cannot allow x,y,z editing as points may get moved outside the node extents
if ( attribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 ||
attribute.name().compare( QLatin1String( "Y" ), Qt::CaseInsensitive ) == 0 ||
attribute.name().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0 )
return false;

if ( !n.isValid() || !mEditIndex.hasNode( n ) ) // todo: should not have to check if n.isValid
return false;

if ( points.isEmpty() )
return false;

const QgsPointCloudAttributeCollection attributeCollection = mEditIndex.attributes();

int attributeOffset;
const QgsPointCloudAttribute *at = attributeCollection.find( attribute.name(), attributeOffset );

const bool success = utils.changeAttributeValue( n, pts, attribute, value );
if ( success )
if ( !at ||
at->size() != attribute.size() ||
at->type() != attribute.type() )
{
emit layerModified();
return false;
}

if ( !QgsPointCloudLayerEditUtils::isAttributeValueValid( attribute, value ) )
{
return false;
}

return success;
QVector<int> sortedPoints( points.constBegin(), points.constEnd() );
std::sort( sortedPoints.begin(), sortedPoints.end() );
sortedPoints.erase( std::unique( sortedPoints.begin(), sortedPoints.end() ), sortedPoints.end() );

if ( sortedPoints.constFirst() < 0 ||
sortedPoints.constLast() > mEditIndex.getNode( n ).pointCount() )
return false;

undoStack()->push( new QgsPointCloudLayerUndoCommandChangeAttribute( mEditIndex, n, sortedPoints, attribute, value ) );

return true;
}

QgsPointCloudIndex QgsPointCloudLayer::index() const
Expand Down
67 changes: 2 additions & 65 deletions src/core/pointcloud/qgspointcloudlayereditutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,72 +14,10 @@
***************************************************************************/

#include "qgspointcloudlayereditutils.h"
#include "qgspointcloudlayer.h"
#include "qgslazdecoder.h"
#include "qgspointcloudattribute.h"
#include "qgspointcloudrequest.h"


QgsPointCloudLayerEditUtils::QgsPointCloudLayerEditUtils( QgsPointCloudLayer *layer )
: mIndex( layer->index() )
{
}

bool QgsPointCloudLayerEditUtils::changeAttributeValue( const QgsPointCloudNodeId &n, const QVector<int> &pts, const QgsPointCloudAttribute &attribute, double value )
{
// Cannot allow x,y,z editing as points may get moved outside the node extents
if ( attribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 ||
attribute.name().compare( QLatin1String( "Y" ), Qt::CaseInsensitive ) == 0 ||
attribute.name().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0 )
return false;

if ( !n.isValid() || !mIndex.hasNode( n ) ) // todo: should not have to check if n.isValid
return false;

const QgsPointCloudAttributeCollection attributeCollection = mIndex.attributes();

int attributeOffset;
const QgsPointCloudAttribute *at = attributeCollection.find( attribute.name(), attributeOffset );

if ( !at ||
at->size() != attribute.size() ||
at->type() != attribute.type() )
{
return false;
}

if ( !isAttributeValueValid( attribute, value ) )
{
return false;
}

const QSet<int> uniquePoints( pts.constBegin(), pts.constEnd() );
QVector<int> sortedPoints( uniquePoints.constBegin(), uniquePoints.constEnd() );
std::sort( sortedPoints.begin(), sortedPoints.end() );

if ( sortedPoints.constFirst() < 0 ||
sortedPoints.constLast() > mIndex.getNode( n ).pointCount() )
return false;

QgsPointCloudRequest req;
req.setAttributes( attributeCollection );

std::unique_ptr<QgsPointCloudBlock> block = mIndex.nodeData( n, req );
const int count = block->pointCount();
const int recordSize = attributeCollection.pointRecordSize();

// copy data
QByteArray data( block->data(), count * recordSize );

char *ptr = data.data();

for ( int i : sortedPoints )
{
// replace attribute for selected point
lazStoreDoubleToStream( ptr, i * recordSize + attributeOffset, attribute.type(), value );
}

return mIndex.updateNodeData( {{n, data}} );;
}

QByteArray QgsPointCloudLayerEditUtils::dataForAttributes( const QgsPointCloudAttributeCollection &allAttributes, const QByteArray &data, const QgsPointCloudRequest &request )
{
const QVector<QgsPointCloudAttribute> attributes = allAttributes.attributes();
Expand All @@ -99,7 +37,6 @@ QByteArray QgsPointCloudLayerEditUtils::dataForAttributes( const QgsPointCloudAt
}
}

//
Q_ASSERT( nPoints == outData.size() / request.attributes().pointRecordSize() );

return outData;
Expand Down
18 changes: 1 addition & 17 deletions src/core/pointcloud/qgspointcloudlayereditutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@
#define QGSPOINTCLOUDLAYEREDITUTILS_H

#include "qgis_core.h"
#include "qgspointcloudindex.h"

#include <QVector>
#include <QByteArray>

#define SIP_NO_FILE

class QgsPointCloudLayer;
class QgsPointCloudNodeId;
class QgsPointCloudAttribute;
class QgsPointCloudAttributeCollection;
Expand All @@ -43,27 +41,13 @@ class CORE_EXPORT QgsPointCloudLayerEditUtils
{
public:
//! Ctor
QgsPointCloudLayerEditUtils( QgsPointCloudLayer *layer );

/**
* Attempts to modify attribute values for specific points in the editing buffer.
*
* \param n The point cloud node containing the points
* \param points The point ids of the points to be modified
* \param attribute The attribute whose value will be updated
* \param value The new value to set to the attribute
* \return TRUE if the editing buffer was updated successfully, FALSE otherwise
*/
bool changeAttributeValue( const QgsPointCloudNodeId &n, const QVector<int> &points, const QgsPointCloudAttribute &attribute, double value );
QgsPointCloudLayerEditUtils() = delete;

//! Takes \a data comprising of \a allAttributes and returns a QByteArray with data only for the attributes included in the \a request
static QByteArray dataForAttributes( const QgsPointCloudAttributeCollection &allAttributes, const QByteArray &data, const QgsPointCloudRequest &request );

//! Check if \a value is within proper range for the \a attribute
static bool isAttributeValueValid( const QgsPointCloudAttribute &attribute, double value );

private:
QgsPointCloudIndex mIndex;
};

#endif // QGSPOINTCLOUDLAYEREDITUTILS_H
79 changes: 79 additions & 0 deletions src/core/pointcloud/qgspointcloudlayerundocommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/***************************************************************************
qgspointcloudlayerundocommand.cpp
---------------------
begin : January 2025
copyright : (C) 2025 by Stefanos Natsis
email : uclaros at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgspointcloudlayerundocommand.h"
#include "qgslazdecoder.h"


QgsPointCloudLayerUndoCommand::QgsPointCloudLayerUndoCommand( QgsPointCloudIndex index )
: mIndex( index )
{}

QgsPointCloudLayerUndoCommandChangeAttribute::QgsPointCloudLayerUndoCommandChangeAttribute( QgsPointCloudIndex index, const QgsPointCloudNodeId &n, const QVector<int> &points, const QgsPointCloudAttribute &attribute, double value )
: QgsPointCloudLayerUndoCommand( index )
, mNode( n )
, mAttribute( attribute )
, mNewValue( value )
{
const QgsPointCloudAttributeCollection allAttributes = mIndex.attributes();
QgsPointCloudRequest req;
req.setAttributes( allAttributes );
std::unique_ptr<QgsPointCloudBlock> block = mIndex.nodeData( n, req );
const char *ptr = block->data();
block->attributes().find( attribute.name(), mAttributeOffset );
const int size = block->pointRecordSize();
for ( const int point : points )
{
const int offset = point * size + mAttributeOffset;
const double oldValue = attribute.convertValueToDouble( ptr + offset );
mPoints.append( qMakePair( point, oldValue ) );
}
}

void QgsPointCloudLayerUndoCommandChangeAttribute::undo()
{
undoRedoPrivate( true );
}

void QgsPointCloudLayerUndoCommandChangeAttribute::redo()
{
undoRedoPrivate( false );
}

void QgsPointCloudLayerUndoCommandChangeAttribute::undoRedoPrivate( bool isUndo )
{
const QgsPointCloudAttributeCollection allAttributes = mIndex.attributes();

QgsPointCloudRequest req;
req.setAttributes( allAttributes );

std::unique_ptr<QgsPointCloudBlock> block = mIndex.nodeData( mNode, req );
const int count = block->pointCount();
const int recordSize = block->pointRecordSize();

// copy data
QByteArray data( block->data(), count * recordSize );

char *ptr = data.data();

for ( const auto &pair : std::as_const( mPoints ) )
{
// replace attribute for selected point
const double value = isUndo ? pair.second : mNewValue;
lazStoreDoubleToStream( ptr, pair.first * recordSize + mAttributeOffset, mAttribute.type(), value );
}

mIndex.updateNodeData( {{mNode, data}} );;
}
57 changes: 57 additions & 0 deletions src/core/pointcloud/qgspointcloudlayerundocommand.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/***************************************************************************
qgspointcloudlayerundocommand.h
---------------------
begin : January 2025
copyright : (C) 2025 by Stefanos Natsis
email : uclaros at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSPOINTCLOUDLAYERUNDOCOMMAND_H
#define QGSPOINTCLOUDLAYERUNDOCOMMAND_H

#include "qgis_core.h"
#include "qgspointcloudindex.h"
#include "qgspointcloudattribute.h"

#include <QUndoCommand>

class CORE_EXPORT QgsPointCloudLayerUndoCommand : public QUndoCommand
{
public:
QgsPointCloudLayerUndoCommand( QgsPointCloudIndex index );

protected:
QgsPointCloudIndex mIndex;
};

class CORE_EXPORT QgsPointCloudLayerUndoCommandChangeAttribute : public QgsPointCloudLayerUndoCommand
{
public:

/**
* Constructor for QgsPointCloudLayerUndoCommandChangeAttribute
* \param buffer associated edit buffer
* \param f feature to add to layer
*/
QgsPointCloudLayerUndoCommandChangeAttribute( QgsPointCloudIndex index, const QgsPointCloudNodeId &n, const QVector<int> &points, const QgsPointCloudAttribute &attribute, double value );

void undo() override;
void redo() override;

private:
void undoRedoPrivate( bool isUndo );

QgsPointCloudNodeId mNode;
QVector< QPair< int, double > > mPoints; // contains pairs of (point number, old value)
QgsPointCloudAttribute mAttribute;
int mAttributeOffset;
double mNewValue;
};
#endif // QGSPOINTCLOUDLAYERUNDOCOMMAND_H
Loading

0 comments on commit 3cb5582

Please sign in to comment.