Merge pull request #32770 from m-kuhn/dxf-symbol-fixes

Make the DXF renderer ready for background threading and fix symbology
This commit is contained in:
Matthias Kuhn 2019-11-14 09:47:22 +01:00 committed by GitHub
commit cbe6150348
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 483 additions and 293 deletions

View File

@ -34,6 +34,17 @@ Returns the layer
%Docstring
Returns the attribute index used to split into multiple layers.
The attribute value is used for layer names.
.. seealso:: :py:func:`splitLayerAttribute`
%End
QString splitLayerAttribute() const;
%Docstring
If the split layer attribute is set, the vector layer
will be split into several dxf layers, one per each
unique value.
.. versionadded:: 3.12
%End
};
@ -85,6 +96,8 @@ The attribute value is used for layer names.
Constructor for QgsDxfExport.
%End
~QgsDxfExport();
void setMapSettings( const QgsMapSettings &settings );
%Docstring
Set map settings and assign layer name attributes
@ -471,6 +484,9 @@ Register name of layer for feature
:param layer: dxf layer of feature
%End
private:
QgsDxfExport( const QgsDxfExport &other );
QgsDxfExport &operator=( const QgsDxfExport & );
};
QFlags<QgsDxfExport::Flag> operator|(QgsDxfExport::Flag f1, QFlags<QgsDxfExport::Flag> f2);

View File

@ -281,14 +281,13 @@ QList< QgsDxfExport::DxfLayer > QgsVectorLayerAndAttributeModel::layers() const
QList< QgsDxfExport::DxfLayer > layers;
QHash< QString, int > layerIdx;
const auto constMCheckedLeafs = mCheckedLeafs;
for ( const QModelIndex &idx : constMCheckedLeafs )
for ( const QModelIndex &idx : qgis::as_const( mCheckedLeafs ) )
{
QgsLayerTreeNode *node = index2node( idx );
if ( QgsLayerTree::isGroup( node ) )
{
const auto constFindLayers = QgsLayerTree::toGroup( node )->findLayers();
for ( QgsLayerTreeLayer *treeLayer : constFindLayers )
const auto childLayers = QgsLayerTree::toGroup( node )->findLayers();
for ( QgsLayerTreeLayer *treeLayer : childLayers )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( treeLayer->layer() );
Q_ASSERT( vl );

View File

@ -61,26 +61,11 @@
#include <QIODevice>
QgsDxfExport::QgsDxfExport( const QgsDxfExport &dxfExport )
{
*this = dxfExport;
}
QgsDxfExport::QgsDxfExport() = default;
QgsDxfExport &QgsDxfExport::operator=( const QgsDxfExport &dxfExport )
QgsDxfExport::~QgsDxfExport()
{
mMapSettings = dxfExport.mMapSettings;
mLayerNameAttribute = dxfExport.mLayerNameAttribute;
mSymbologyScale = dxfExport.mSymbologyScale;
mSymbologyExport = dxfExport.mSymbologyExport;
mMapUnits = dxfExport.mMapUnits;
mLayerTitleAsName = dxfExport.mLayerTitleAsName;
mSymbolLayerCounter = 0; // internal counter
mNextHandleId = 0;
mBlockCounter = 0;
mCrs = QgsCoordinateReferenceSystem();
mFactor = dxfExport.mFactor;
mForce2d = dxfExport.mForce2d;
return *this;
qDeleteAll( mJobs );
}
void QgsDxfExport::setMapSettings( const QgsMapSettings &settings )
@ -248,10 +233,12 @@ QgsDxfExport::ExportResult QgsDxfExport::writeToFile( QIODevice *d, const QStrin
mMapSettings.setOutputDpi( dpi );
writeHeader( dxfEncoding( encoding ) );
prepareRenderers();
writeTables();
writeBlocks();
writeEntities();
writeEndFile();
stopRenderers();
return ExportResult::Success;
}
@ -560,7 +547,7 @@ void QgsDxfExport::writeBlocks()
startSection();
writeGroup( 2, QStringLiteral( "BLOCKS" ) );
const QStringList blockStrings = QStringList() << QStringLiteral( "*Model_Space" ) << QStringLiteral( "*Paper_Space" ) << QStringLiteral( "*Paper_Space0" );
static const QStringList blockStrings = QStringList() << QStringLiteral( "*Model_Space" ) << QStringLiteral( "*Paper_Space" ) << QStringLiteral( "*Paper_Space0" );
for ( const QString &block : blockStrings )
{
writeGroup( 0, QStringLiteral( "BLOCK" ) );
@ -598,13 +585,11 @@ void QgsDxfExport::writeBlocks()
// if point symbol layer and no data defined properties: write block
QgsSymbolRenderContext ctx( ct, QgsUnitTypes::RenderMapUnits, symbolLayer.second->opacity(), false, symbolLayer.second->renderHints(), nullptr );
ml->startRender( ctx );
// markers with data defined properties are inserted inline
if ( hasDataDefinedProperties( ml, symbolLayer.second ) )
{
continue;
// ml->stopRender( ctx );
}
QString block( QStringLiteral( "symbolLayer%1" ).arg( mBlockCounter++ ) );
@ -637,7 +622,6 @@ void QgsDxfExport::writeBlocks()
writeGroup( 100, QStringLiteral( "AcDbBlockEnd" ) );
mPointSymbolBlocks.insert( ml, block );
ml->stopRender( ctx );
}
endSection();
}
@ -650,127 +634,36 @@ void QgsDxfExport::writeEntities()
mBlockHandle = QStringLiteral( "%1" ).arg( mBlockHandles[ QStringLiteral( "*Model_Space" )], 0, 16 );
QImage image( 10, 10, QImage::Format_ARGB32_Premultiplied );
image.setDotsPerMeterX( 96 / 25.4 * 1000 );
image.setDotsPerMeterY( 96 / 25.4 * 1000 );
QPainter painter( &image );
QgsRenderContext ctx;
ctx.setPainter( &painter );
ctx.setRendererScale( mSymbologyScale );
ctx.setExtent( mExtent );
ctx.setScaleFactor( 96.0 / 25.4 );
ctx.setMapToPixel( QgsMapToPixel( 1.0 / mFactor, mExtent.center().x(), mExtent.center().y(), mExtent.width() * mFactor,
mExtent.height() * mFactor, 0 ) );
ctx.expressionContext().appendScope( QgsExpressionContextUtils::projectScope( QgsProject::instance() ) );
ctx.expressionContext().appendScope( QgsExpressionContextUtils::globalScope() );
// label engine
QgsDefaultLabelingEngine engine;
engine.setMapSettings( mMapSettings );
// iterate through the maplayers
const QList< QgsMapLayer *> layers = mMapSettings.layers();
for ( QgsMapLayer *ml : layers )
for ( DxfLayerJob *job : qgis::as_const( mJobs ) )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
if ( !vl || !layerIsScaleBasedVisible( vl ) )
{
continue;
}
QgsMapLayerStyleOverride styleOverride( vl );
if ( mMapSettings.layerStyleOverrides().contains( vl->id() ) )
{
QgsDebugMsg( QStringLiteral( "%1: apply override style" ).arg( vl->id() ) );
styleOverride.setOverrideStyle( mMapSettings.layerStyleOverrides().value( vl->id() ) );
}
else
{
QgsDebugMsg( QStringLiteral( "%1: no override style" ).arg( vl->id() ) );
}
if ( !vl->renderer() )
{
continue;
}
auto scopePopper = [&ctx]( QgsExpressionContextScope * scope )
{
Q_UNUSED( scope )
delete ctx.expressionContext().popScope();
};
std::unique_ptr<QgsExpressionContextScope, decltype( scopePopper ) > layerScope( QgsExpressionContextUtils::layerScope( ml ), scopePopper );
ctx.expressionContext().appendScope( layerScope.get() );
QgsSymbolRenderContext sctx( ctx, QgsUnitTypes::RenderMillimeters, 1.0, false, nullptr, nullptr );
std::unique_ptr< QgsFeatureRenderer > renderer( vl->renderer()->clone() );
renderer->startRender( ctx, vl->fields() );
QSet<QString> attributes = renderer->usedAttributes( ctx );
int attrIdx = mLayerNameAttribute.value( vl->id(), -1 );
if ( vl->fields().exists( attrIdx ) )
{
QString layerAttr = vl->fields().at( attrIdx ).name();
attributes << layerAttr;
}
const QgsAbstractVectorLayerLabeling *labeling = vl->labelsEnabled() ? vl->labeling() : nullptr;
QgsDxfLabelProvider *lp = nullptr;
QgsDxfRuleBasedLabelProvider *rblp = nullptr;
if ( const QgsRuleBasedLabeling *rbl = dynamic_cast<const QgsRuleBasedLabeling *>( labeling ) )
{
rblp = new QgsDxfRuleBasedLabelProvider( *rbl, vl, this );
rblp->reinit( vl );
engine.addProvider( rblp );
if ( !rblp->prepare( ctx, attributes ) )
{
engine.removeProvider( rblp );
rblp = nullptr;
}
}
else if ( labeling )
{
QgsPalLayerSettings settings = labeling->settings();
lp = new QgsDxfLabelProvider( vl, QString(), this, &settings );
engine.addProvider( lp );
if ( !lp->prepare( ctx, attributes ) )
{
engine.removeProvider( lp );
lp = nullptr;
}
}
QgsSymbolRenderContext sctx( mRenderContext, QgsUnitTypes::RenderMillimeters, 1.0, false, nullptr, nullptr );
if ( mSymbologyExport == QgsDxfExport::SymbolLayerSymbology &&
( renderer->capabilities() & QgsFeatureRenderer::SymbolLevels ) &&
renderer->usingSymbolLevels() )
( job->renderer->capabilities() & QgsFeatureRenderer::SymbolLevels ) &&
job->renderer->usingSymbolLevels() )
{
writeEntitiesSymbolLevels( vl );
renderer->stopRender( ctx );
writeEntitiesSymbolLevels( job );
continue;
}
QgsFeatureRequest freq = QgsFeatureRequest().setSubsetOfAttributes( attributes, vl->fields() ).setExpressionContext( ctx.expressionContext() );
freq.setFilterRect( mMapSettings.mapToLayerCoordinates( vl, mExtent ) );
QgsCoordinateTransform ct( mMapSettings.destinationCrs(), job->crs, mMapSettings.transformContext() );
QgsFeatureIterator featureIt = vl->getFeatures( freq );
QgsFeatureRequest request = QgsFeatureRequest().setSubsetOfAttributes( job->attributes, job->fields ).setExpressionContext( job->expressionContext );
request.setFilterRect( ct.transform( mExtent ) );
QgsCoordinateTransform ct = mMapSettings.layerTransform( vl );
QgsFeatureIterator featureIt = job->featureSource.getFeatures( request );
QgsFeature fet;
while ( featureIt.nextFeature( fet ) )
{
ctx.expressionContext().setFeature( fet );
QString lName( dxfLayerName( attrIdx < 0 ? layerName( vl ) : fet.attribute( attrIdx ).toString() ) );
mRenderContext.expressionContext().setFeature( fet );
QString lName( dxfLayerName( job->splitLayerAttribute.isNull() ? job->layerTitle : fet.attribute( job->splitLayerAttribute ).toString() ) );
sctx.setFeature( &fet );
if ( !renderer->willRenderFeature( fet, ctx ) )
if ( !job->renderer->willRenderFeature( fet, mRenderContext ) )
continue;
if ( mSymbologyExport == NoSymbology )
@ -779,7 +672,7 @@ void QgsDxfExport::writeEntities()
}
else
{
const QgsSymbolList symbolList = renderer->symbolsForFeature( fet, ctx );
const QgsSymbolList symbolList = job->renderer->symbolsForFeature( fet, mRenderContext );
bool hasSymbology = symbolList.size() > 0;
if ( hasSymbology && mSymbologyExport == QgsDxfExport::SymbolLayerSymbology ) // symbol layer symbology, but layer does not use symbol levels
@ -823,55 +716,88 @@ void QgsDxfExport::writeEntities()
}
}
if ( lp )
if ( job->labelProvider )
{
lp->registerDxfFeature( fet, ctx, lName );
job->labelProvider->registerDxfFeature( fet, mRenderContext, lName );
}
else if ( rblp )
else if ( job->ruleBasedLabelProvider )
{
rblp->registerDxfFeature( fet, ctx, lName );
job->ruleBasedLabelProvider->registerDxfFeature( fet, mRenderContext, lName );
}
}
}
renderer->stopRender( ctx );
}
engine.run( ctx );
QImage image( 10, 10, QImage::Format_ARGB32_Premultiplied );
image.setDotsPerMeterX( 96 / 25.4 * 1000 );
image.setDotsPerMeterY( 96 / 25.4 * 1000 );
QPainter painter( &image );
mRenderContext.setPainter( &painter );
mRenderContext.labelingEngine()->run( mRenderContext );
endSection();
}
void QgsDxfExport::writeEntitiesSymbolLevels( QgsVectorLayer *layer )
void QgsDxfExport::prepareRenderers()
{
if ( !layer )
{
return;
}
Q_ASSERT( mJobs.empty() ); // If this fails, stopRenderers() was not called after the last job
if ( !layer->renderer() )
mRenderContext = QgsRenderContext();
mRenderContext.setRendererScale( mSymbologyScale );
mRenderContext.setExtent( mExtent );
mRenderContext.setScaleFactor( 96.0 / 25.4 );
mRenderContext.setMapToPixel( QgsMapToPixel( 1.0 / mFactor, mExtent.center().x(), mExtent.center().y(), mExtent.width() * mFactor,
mExtent.height() * mFactor, 0 ) );
mRenderContext.expressionContext().appendScope( QgsExpressionContextUtils::projectScope( QgsProject::instance() ) );
mRenderContext.expressionContext().appendScope( QgsExpressionContextUtils::globalScope() );
mLabelingEngine = qgis::make_unique<QgsDefaultLabelingEngine>();
mLabelingEngine->setMapSettings( mMapSettings );
mRenderContext.setLabelingEngine( mLabelingEngine.get() );
const QList< QgsMapLayer * > layers = mMapSettings.layers();
for ( QgsMapLayer *ml : layers )
{
// TODO return error
return;
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
if ( !vl )
continue;
if ( !vl->renderer() )
continue;
if ( !layerIsScaleBasedVisible( vl ) )
continue;
QString splitLayerAttribute;
int splitLayerAttributeIndex = mLayerNameAttribute.value( vl->id(), -1 );
const QgsFields fields = vl->fields();
if ( splitLayerAttributeIndex >= 0 && splitLayerAttributeIndex < fields.size() )
splitLayerAttribute = fields.at( splitLayerAttributeIndex ).name();
DxfLayerJob *job = new DxfLayerJob( vl, mMapSettings.layerStyleOverrides().value( vl->id() ), mRenderContext, this, splitLayerAttribute );
mJobs.append( job );
}
std::unique_ptr< QgsFeatureRenderer > renderer( layer->renderer()->clone() );
}
void QgsDxfExport::writeEntitiesSymbolLevels( DxfLayerJob *job )
{
QHash< QgsSymbol *, QList<QgsFeature> > features;
QgsRenderContext ctx = renderContext();
ctx.expressionContext().appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
const QList<QgsExpressionContextScope *> scopes = job->expressionContext.scopes();
for ( QgsExpressionContextScope *scope : scopes )
ctx.expressionContext().appendScope( new QgsExpressionContextScope( *scope ) );
QgsSymbolRenderContext sctx( ctx, QgsUnitTypes::RenderMillimeters, 1.0, false, nullptr, nullptr );
renderer->startRender( ctx, layer->fields() );
// get iterator
QgsFeatureRequest req;
if ( layer->wkbType() == QgsWkbTypes::NoGeometry )
{
req.setFlags( QgsFeatureRequest::NoGeometry );
}
req.setSubsetOfAttributes( renderer->usedAttributes( ctx ), layer->fields() );
req.setFilterRect( mMapSettings.mapToLayerCoordinates( layer, mExtent ) );
req.setSubsetOfAttributes( job->renderer->usedAttributes( ctx ), job->featureSource.fields() );
QgsCoordinateTransform ct( mMapSettings.destinationCrs(), job->crs, mMapSettings.transformContext() );
req.setFilterRect( ct.transform( mExtent ) );
QgsFeatureIterator fit = layer->getFeatures( req );
QgsFeatureIterator fit = job->featureSource.getFeatures( req );
// fetch features
QgsFeature fet;
@ -879,7 +805,7 @@ void QgsDxfExport::writeEntitiesSymbolLevels( QgsVectorLayer *layer )
while ( fit.nextFeature( fet ) )
{
ctx.expressionContext().setFeature( fet );
featureSymbol = renderer->symbolForFeature( fet, ctx );
featureSymbol = job->renderer->symbolForFeature( fet, ctx );
if ( !featureSymbol )
{
continue;
@ -895,7 +821,7 @@ void QgsDxfExport::writeEntitiesSymbolLevels( QgsVectorLayer *layer )
// find out order
QgsSymbolLevelOrder levels;
const QgsSymbolList symbols = renderer->symbols( ctx );
const QgsSymbolList symbols = job->renderer->symbols( ctx );
for ( QgsSymbol *symbol : symbols )
{
for ( int j = 0; j < symbol->symbolLayerCount(); j++ )
@ -910,8 +836,6 @@ void QgsDxfExport::writeEntitiesSymbolLevels( QgsVectorLayer *layer )
}
}
QgsCoordinateTransform ct = mMapSettings.layerTransform( layer );
// export symbol layers and symbology
for ( const QgsSymbolLevel &level : qgis::as_const( levels ) )
{
@ -928,11 +852,16 @@ void QgsDxfExport::writeEntitiesSymbolLevels( QgsVectorLayer *layer )
for ( const QgsFeature &feature : featureList )
{
sctx.setFeature( &feature );
addFeature( sctx, ct, layer->name(), levelIt.key()->symbolLayer( llayer ), levelIt.key() );
addFeature( sctx, ct, job->layerName, levelIt.key()->symbolLayer( llayer ), levelIt.key() );
}
}
}
renderer->stopRender( ctx );
}
void QgsDxfExport::stopRenderers()
{
qDeleteAll( mJobs );
mJobs.clear();
}
void QgsDxfExport::writeEndFile()
@ -1803,9 +1732,7 @@ QRgb QgsDxfExport::createRgbEntry( qreal r, qreal g, qreal b )
QgsRenderContext QgsDxfExport::renderContext() const
{
QgsRenderContext context;
context.setRendererScale( mSymbologyScale );
return context;
return mRenderContext;
}
double QgsDxfExport::mapUnitScaleFactor( double scale, QgsUnitTypes::RenderUnit symbolUnits, QgsUnitTypes::DistanceUnit mapUnits, double mapUnitsPerPixel )
@ -1861,35 +1788,20 @@ QList< QPair< QgsSymbolLayer *, QgsSymbol * > > QgsDxfExport::symbolLayers( QgsR
{
QList< QPair< QgsSymbolLayer *, QgsSymbol * > > symbolLayers;
const QList< QgsMapLayer * > layers = mMapSettings.layers();
for ( QgsMapLayer *ml : layers )
for ( DxfLayerJob *job : mJobs )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
if ( !vl )
{
continue;
}
const QgsSymbolList symbols = job->renderer->symbols( context );
// get renderer
QgsFeatureRenderer *r = vl->renderer();
if ( !r )
for ( QgsSymbol *symbol : symbols )
{
continue;
}
// get all symbols
QgsSymbolList symbols = r->symbols( context );
QgsSymbolList::iterator symbolIt = symbols.begin();
for ( ; symbolIt != symbols.end(); ++symbolIt )
{
int maxSymbolLayers = ( *symbolIt )->symbolLayerCount();
int maxSymbolLayers = symbol->symbolLayerCount();
if ( mSymbologyExport != SymbolLayerSymbology )
{
maxSymbolLayers = 1;
}
for ( int i = 0; i < maxSymbolLayers; ++i )
{
symbolLayers.append( qMakePair( ( *symbolIt )->symbolLayer( i ), *symbolIt ) );
symbolLayers.append( qMakePair( symbol->symbolLayer( i ), symbol ) );
}
}
}
@ -1900,8 +1812,7 @@ QList< QPair< QgsSymbolLayer *, QgsSymbol * > > QgsDxfExport::symbolLayers( QgsR
void QgsDxfExport::writeDefaultLinetypes()
{
// continuous (Qt solid line)
const QStringList blockStrings = QStringList() << QStringLiteral( "ByLayer" ) << QStringLiteral( "ByBlock" ) << QStringLiteral( "CONTINUOUS" );
for ( const QString &ltype : blockStrings )
for ( const QString &ltype : { QStringLiteral( "ByLayer" ), QStringLiteral( "ByBlock" ), QStringLiteral( "CONTINUOUS" ) } )
{
writeGroup( 0, QStringLiteral( "LTYPE" ) );
writeHandle();
@ -1983,10 +1894,9 @@ int QgsDxfExport::nLineTypes( const QList< QPair< QgsSymbolLayer *, QgsSymbol *
void QgsDxfExport::writeLinetype( const QString &styleName, const QVector<qreal> &pattern, QgsUnitTypes::RenderUnit u )
{
double length = 0;
QVector<qreal>::const_iterator dashIt = pattern.constBegin();
for ( ; dashIt != pattern.constEnd(); ++dashIt )
for ( qreal size : pattern )
{
length += ( *dashIt * mapUnitScaleFactor( mSymbologyScale, u, mMapUnits, mMapSettings.mapToPixel().mapUnitsPerPixel() ) );
length += ( size * mapUnitScaleFactor( mSymbologyScale, u, mMapUnits, mMapSettings.mapToPixel().mapUnitsPerPixel() ) );
}
writeGroup( 0, QStringLiteral( "LTYPE" ) );
@ -2001,12 +1911,11 @@ void QgsDxfExport::writeLinetype( const QString &styleName, const QVector<qreal>
writeGroup( 73, pattern.size() );
writeGroup( 40, length );
dashIt = pattern.constBegin();
bool isGap = false;
for ( ; dashIt != pattern.constEnd(); ++dashIt )
for ( qreal size : pattern )
{
// map units or mm?
double segmentLength = ( isGap ? -*dashIt : *dashIt );
double segmentLength = ( isGap ? -size : size );
segmentLength *= mapUnitScaleFactor( mSymbologyScale, u, mMapUnits, mMapSettings.mapToPixel().mapUnitsPerPixel() );
writeGroup( 49, segmentLength );
writeGroup( 74, 0 );
@ -2156,6 +2065,7 @@ bool QgsDxfExport::layerIsScaleBasedVisible( const QgsMapLayer *layer ) const
QString QgsDxfExport::layerName( const QString &id, const QgsFeature &f ) const
{
// TODO: make this thread safe
const QList< QgsMapLayer * > layers = mMapSettings.layers();
for ( QgsMapLayer *ml : layers )
{
@ -2378,3 +2288,15 @@ QgsCoordinateReferenceSystem QgsDxfExport::destinationCrs() const
{
return mCrs;
}
QString QgsDxfExport::DxfLayer::splitLayerAttribute() const
{
QString splitLayerFieldName;
const QgsFields fields = mLayer->fields();
if ( mLayerOutputAttributeIndex >= 0 && mLayerOutputAttributeIndex < fields.size() )
{
splitLayerFieldName = fields.at( mLayerOutputAttributeIndex ).name();
}
return splitLayerFieldName;
}

View File

@ -38,6 +38,7 @@ class QgsCurve;
class QgsCurvePolygon;
class QgsCircularString;
class QgsCompoundCurve;
struct DxfLayerJob;
#define DXF_HANDSEED 100
#define DXF_HANDMAX 9999999
@ -73,9 +74,18 @@ class CORE_EXPORT QgsDxfExport
/**
* Returns the attribute index used to split into multiple layers.
* The attribute value is used for layer names.
* \see splitLayerAttribute
*/
int layerOutputAttributeIndex() const {return mLayerOutputAttributeIndex;}
/**
* If the split layer attribute is set, the vector layer
* will be split into several dxf layers, one per each
* unique value.
* \since QGIS 3.12
*/
QString splitLayerAttribute() const;
private:
QgsVectorLayer *mLayer = nullptr;
int mLayerOutputAttributeIndex = -1;
@ -135,9 +145,9 @@ class CORE_EXPORT QgsDxfExport
/**
* Constructor for QgsDxfExport.
*/
QgsDxfExport() = default;
QgsDxfExport( const QgsDxfExport &dxfExport ) SIP_SKIP;
QgsDxfExport &operator=( const QgsDxfExport &dxfExport );
QgsDxfExport();
~QgsDxfExport();
/**
* Set map settings and assign layer name attributes
@ -493,6 +503,11 @@ class CORE_EXPORT QgsDxfExport
void registerDxfLayer( const QString &layerId, QgsFeatureId fid, const QString &layer );
private:
#ifdef SIP_RUN
QgsDxfExport( const QgsDxfExport &other );
QgsDxfExport &operator=( const QgsDxfExport & );
#endif
//! Extent for export, only intersecting features are exported. If the extent is an empty rectangle, all features are exported
QgsRectangle mExtent;
//! Scale for symbology export (used if symbols units are mm)
@ -512,10 +527,12 @@ class CORE_EXPORT QgsDxfExport
//AC1009
void writeHeader( const QString &codepage );
void prepareRenderers();
void writeTables();
void writeBlocks();
void writeEntities();
void writeEntitiesSymbolLevels( QgsVectorLayer *layer );
void writeEntitiesSymbolLevels( DxfLayerJob *job );
void stopRenderers();
void writeEndFile();
void startSection();
@ -581,6 +598,11 @@ class CORE_EXPORT QgsDxfExport
void appendLineString( const QgsLineString &ls, QVector<QgsPoint> &points, QVector<double> &bulges );
void appendCircularString( const QgsCircularString &cs, QVector<QgsPoint> &points, QVector<double> &bulges );
void appendCompoundCurve( const QgsCompoundCurve &cc, QVector<QgsPoint> &points, QVector<double> &bulges );
QgsRenderContext mRenderContext;
// Internal cache for layer related information required during rendering
QList<DxfLayerJob *> mJobs;
std::unique_ptr<QgsLabelingEngine> mLabelingEngine;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsDxfExport::Flags )

View File

@ -15,6 +15,96 @@
* *
***************************************************************************/
#include "qgsvectorlayer.h"
#include "qgsexpressioncontext.h"
#include "qgsexpressioncontextutils.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsrenderer.h"
#include "qgsvectorlayerlabeling.h"
#include "qgsdxfpallabeling.h"
/**
* Holds information about each layer in a DXF job.
* This can be used for multithreading.
*/
struct DxfLayerJob
{
DxfLayerJob( QgsVectorLayer *vl, const QString &layerStyleOverride, QgsRenderContext &renderContext, QgsDxfExport *dxfExport, const QString &splitLayerAttribute )
: styleOverride( vl )
, expressionContext( renderContext.expressionContext() )
, featureSource( vl )
, dxfExport( dxfExport )
, crs( vl->crs() )
, layerName( vl->name() )
, splitLayerAttribute( splitLayerAttribute )
, layerTitle( vl->title().isEmpty() ? vl->name() : vl->title() )
{
fields = vl->fields();
renderer.reset( vl->renderer()->clone() );
expressionContext.appendScope( vl->createExpressionContextScope() );
if ( !layerStyleOverride.isNull() )
{
styleOverride.setOverrideStyle( layerStyleOverride );
}
labeling.reset( vl->labelsEnabled() ? vl->labeling()->clone() : nullptr );
attributes = renderer->usedAttributes( renderContext );
if ( !splitLayerAttribute.isNull() )
{
attributes << splitLayerAttribute;
}
if ( labeling )
{
QgsLabelingEngine *labelingEngine = renderContext.labelingEngine();
if ( const QgsRuleBasedLabeling *rbl = dynamic_cast<const QgsRuleBasedLabeling *>( labeling.get() ) )
{
ruleBasedLabelProvider = new QgsDxfRuleBasedLabelProvider( *rbl, vl, dxfExport );
labelingEngine->addProvider( ruleBasedLabelProvider );
if ( !ruleBasedLabelProvider->prepare( renderContext, attributes ) )
{
labelingEngine->removeProvider( ruleBasedLabelProvider );
ruleBasedLabelProvider = nullptr;
}
}
else
{
QgsPalLayerSettings settings = labeling->settings();
labelProvider = new QgsDxfLabelProvider( vl, QString(), dxfExport, &settings );
labelingEngine->addProvider( labelProvider );
if ( !labelProvider->prepare( renderContext, attributes ) )
{
labelingEngine->removeProvider( labelProvider );
labelProvider = nullptr;
}
}
}
// This will need to be started in a separate thread, if threaded somewhere else to
renderer->startRender( renderContext, fields );
};
QgsFields fields;
QgsMapLayerStyleOverride styleOverride;
QgsExpressionContext expressionContext;
QgsVectorLayerFeatureSource featureSource;
std::unique_ptr< QgsFeatureRenderer > renderer;
std::unique_ptr<QgsAbstractVectorLayerLabeling> labeling;
QgsDxfExport *dxfExport = nullptr;
QgsCoordinateReferenceSystem crs;
QString layerName;
QgsDxfLabelProvider *labelProvider = nullptr;
QgsDxfRuleBasedLabelProvider *ruleBasedLabelProvider = nullptr;
QString splitLayerAttribute;
QString layerTitle;
QSet<QString> attributes;
};
// dxf color palette
static int sDxfColors[][3] =
{

View File

@ -44,6 +44,7 @@ QgsDxfRuleBasedLabelProvider::QgsDxfRuleBasedLabelProvider( const QgsRuleBasedLa
: QgsRuleBasedLabelProvider( rules, layer, false )
, mDxfExport( dxf )
{
mRules->rootRule()->createSubProviders( layer, mSubProviders, this );
}
void QgsDxfRuleBasedLabelProvider::reinit( QgsVectorLayer *layer )

View File

@ -78,8 +78,9 @@ class QgsDxfRuleBasedLabelProvider : public QgsRuleBasedLabelProvider
/**
* Reinitialize the subproviders with QgsDxfLabelProviders
* \param layer layer
* \deprecated since QGIS 3.12
*/
void reinit( QgsVectorLayer *layer );
Q_DECL_DEPRECATED void reinit( QgsVectorLayer *layer );
/**
* Re-implementation that writes to DXF file instead of drawing with QPainter

View File

@ -489,7 +489,7 @@ class CORE_EXPORT QgsRenderContext
* Assign new labeling engine
* \note not available in Python bindings
*/
void setLabelingEngine( QgsLabelingEngine *engine2 ) { mLabelingEngine = engine2; } SIP_SKIP
void setLabelingEngine( QgsLabelingEngine *engine ) { mLabelingEngine = engine; } SIP_SKIP
/**
* Sets the \a color to use when rendering selected features.

View File

@ -38,8 +38,8 @@ namespace QgsWms
// Write output
QgsRenderer renderer( context );
QgsDxfExport dxf = renderer.getDxf();
std::unique_ptr<QgsDxfExport> dxf = renderer.getDxf();
response.setHeader( "Content-Type", "application/dxf" );
dxf.writeToFile( response.io(), parameters.dxfCodec() );
dxf->writeToFile( response.io(), parameters.dxfCodec() );
}
} // namespace QgsWms

View File

@ -804,7 +804,7 @@ namespace QgsWms
return image.release();
}
QgsDxfExport QgsRenderer::getDxf()
std::unique_ptr<QgsDxfExport> QgsRenderer::getDxf()
{
// init layer restorer before doing anything
std::unique_ptr<QgsLayerRestorer> restorer;
@ -838,14 +838,14 @@ namespace QgsWms
}
// add layers to dxf
QgsDxfExport dxf;
dxf.setExtent( mWmsParameters.bboxAsRectangle() );
dxf.addLayers( dxfLayers );
dxf.setLayerTitleAsName( mWmsParameters.dxfUseLayerTitleAsName() );
dxf.setSymbologyExport( mWmsParameters.dxfMode() );
std::unique_ptr<QgsDxfExport> dxf = qgis::make_unique<QgsDxfExport>();
dxf->setExtent( mWmsParameters.bboxAsRectangle() );
dxf->addLayers( dxfLayers );
dxf->setLayerTitleAsName( mWmsParameters.dxfUseLayerTitleAsName() );
dxf->setSymbologyExport( mWmsParameters.dxfMode() );
if ( mWmsParameters.dxfFormatOptions().contains( QgsWmsParameters::DxfFormatOption::SCALE ) )
{
dxf.setSymbologyScale( mWmsParameters.dxfScale() );
dxf->setSymbologyScale( mWmsParameters.dxfScale() );
}
return dxf;

View File

@ -124,7 +124,7 @@ namespace QgsWms
* \returns the map as DXF data
* \since QGIS 3.0
*/
QgsDxfExport getDxf();
std::unique_ptr<QgsDxfExport> getDxf();
/**
* Returns printed page as binary

View File

@ -28,6 +28,7 @@
#include "qgslabelingengine.h"
#include "qgssinglesymbolrenderer.h"
#include "qgsvectorlayerlabeling.h"
#include "qgslinesymbollayer.h"
#include <QTemporaryFile>
Q_DECLARE_METATYPE( QgsDxfExport::HAlign )
@ -49,7 +50,7 @@ class TestQgsDxfExport : public QObject
void testPolygons();
void testMultiSurface();
void testMtext();
void testMTextNoSymbology(); //tests if label export works if layer has vector renderer type 'no symbols'
void testMtext_data();
void testMTextEscapeSpaces();
void testText();
void testTextAlign();
@ -57,6 +58,7 @@ class TestQgsDxfExport : public QObject
void testGeometryGeneratorExport();
void testCurveExport();
void testCurveExport_data();
void testDashedLine();
private:
QgsVectorLayer *mPointLayer = nullptr;
@ -71,7 +73,6 @@ class TestQgsDxfExport : public QObject
QString getTempFileName( const QString &file ) const;
bool fileContainsText( const QString &path, const QString &text, QString *debugInfo = nullptr ) const;
bool testMtext( QgsVectorLayer *vlayer, const QString &tempFileName ) const;
};
void TestQgsDxfExport::initTestCase()
@ -264,12 +265,98 @@ void TestQgsDxfExport::testMultiSurface()
void TestQgsDxfExport::testMtext()
{
QVERIFY( testMtext( mPointLayer, QStringLiteral( "mtext_dxf" ) ) );
QFETCH( QgsVectorLayer *, layer );
QFETCH( QString, layerName );
QVERIFY( layer );
QgsProject::instance()->addMapLayer( layer );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
layer->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
layer->setLabelsEnabled( true );
QgsDxfExport d;
d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( layer ) );
QgsMapSettings mapSettings;
QSize size( 640, 480 );
mapSettings.setOutputSize( size );
mapSettings.setExtent( layer->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << layer );
mapSettings.setOutputDpi( 96 );
mapSettings.setDestinationCrs( layer->crs() );
d.setMapSettings( mapSettings );
d.setSymbologyScale( 1000 );
d.setSymbologyExport( QgsDxfExport::FeatureSymbology );
QString file = getTempFileName( layerName );
QFile dxfFile( file );
QCOMPARE( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success );
dxfFile.close();
QString debugInfo;
QVERIFY2( fileContainsText( file, "MTEXT\n"
" 5\n"
"**no check**\n"
"100\n"
"AcDbEntity\n"
"100\n"
"AcDbMText\n"
" 8\n"
"points\n"
"420\n"
"**no check**\n"
" 10\n"
"**no check**\n"
" 20\n"
"**no check**\n"
" 1\n"
"\\fQGIS Vera Sans|i0|b1;\\H3.81136;Biplane\n"
" 50\n"
"0.0\n"
" 41\n"
"**no check**\n"
" 71\n"
" 7\n"
" 7\n"
"STANDARD\n"
" 0", &debugInfo ), debugInfo.toUtf8().constData() );
QgsProject::instance()->removeMapLayer( layer );
}
void TestQgsDxfExport::testMTextNoSymbology()
void TestQgsDxfExport::testMtext_data()
{
QVERIFY( testMtext( mPointLayerNoSymbols, QStringLiteral( "text_no_symbology_dxf" ) ) );
QTest::addColumn<QgsVectorLayer *>( "layer" );
QTest::addColumn<QString>( "layerName" );
QString filename = QStringLiteral( TEST_DATA_DIR ) + "/points.shp";
QgsVectorLayer *pointLayer = new QgsVectorLayer( filename, QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
QVERIFY( pointLayer->isValid() );
QTest::newRow( "MText" )
<< pointLayer
<< QStringLiteral( "mtext_dxf" );
QgsVectorLayer *pointLayerNoSymbols = new QgsVectorLayer( filename, QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
QVERIFY( pointLayerNoSymbols->isValid() );
pointLayerNoSymbols->setRenderer( new QgsNullSymbolRenderer() );
pointLayerNoSymbols->addExpressionField( QStringLiteral( "'A text with spaces'" ), QgsField( QStringLiteral( "Spacestest" ), QVariant::String ) );
QTest::newRow( "MText No Symbology" )
<< pointLayerNoSymbols
<< QStringLiteral( "mtext_no_symbology_dxf" );
}
void TestQgsDxfExport::testMTextEscapeSpaces()
@ -304,7 +391,8 @@ void TestQgsDxfExport::testMTextEscapeSpaces()
QFile dxfFile( file );
QCOMPARE( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success );
dxfFile.close();
QVERIFY( fileContainsText( file, "\\fQGIS Vera Sans|i0|b1;\\H3.81136;A\\~text\\~with\\~spaces" ) );
QString debugInfo;
QVERIFY2( fileContainsText( file, "\\fQGIS Vera Sans|i0|b1;\\H3.81136;A\\~text\\~with\\~spaces", &debugInfo ), debugInfo.toUtf8().constData() );
}
void TestQgsDxfExport::testText()
@ -436,7 +524,7 @@ void TestQgsDxfExport::testTextAlign()
QString debugInfo;
QVERIFY2( fileContainsText( file, QStringLiteral( "TEXT\n"
" 5\n"
"89\n"
"**no check**\n"
"100\n"
"AcDbEntity\n"
"100\n"
@ -519,76 +607,6 @@ void TestQgsDxfExport::testTextAlign_data()
<< QStringLiteral( "Half" );
}
bool TestQgsDxfExport::testMtext( QgsVectorLayer *vlayer, const QString &tempFileName ) const
{
if ( !vlayer )
{
return false;
}
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "Class" );
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
vlayer->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
vlayer->setLabelsEnabled( true );
QgsDxfExport d;
d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( vlayer ) );
QgsMapSettings mapSettings;
QSize size( 640, 480 );
mapSettings.setOutputSize( size );
mapSettings.setExtent( vlayer->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vlayer );
mapSettings.setOutputDpi( 96 );
mapSettings.setDestinationCrs( vlayer->crs() );
d.setMapSettings( mapSettings );
d.setSymbologyScale( 1000 );
d.setSymbologyExport( QgsDxfExport::FeatureSymbology );
QString file = getTempFileName( tempFileName );
QFile dxfFile( file );
if ( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ) != QgsDxfExport::ExportResult::Success )
{
return false;
}
dxfFile.close();
return ( fileContainsText( file, "MTEXT\n"
" 5\n"
"**no check**\n"
"100\n"
"AcDbEntity\n"
"100\n"
"AcDbMText\n"
" 8\n"
"points\n"
"420\n"
"**no check**\n"
" 10\n"
"**no check**\n"
" 20\n"
"**no check**\n"
" 1\n"
"\\fQGIS Vera Sans|i0|b1;\\H3.81136;Biplane\n"
" 50\n"
"0.0\n"
" 41\n"
"**no check**\n"
" 71\n"
" 7\n"
" 7\n"
"STANDARD\n"
" 0" ) );
}
void TestQgsDxfExport::testGeometryGeneratorExport()
{
QgsDxfExport d;
@ -747,6 +765,127 @@ void TestQgsDxfExport::testCurveExport_data()
}
void TestQgsDxfExport::testDashedLine()
{
std::unique_ptr<QgsSimpleLineSymbolLayer> symbolLayer = qgis::make_unique<QgsSimpleLineSymbolLayer>( QColor( 0, 0, 0 ) );
symbolLayer->setWidth( 0.11 );
symbolLayer->setCustomDashVector( { 0.5, 0.35 } );
symbolLayer->setCustomDashPatternUnit( QgsUnitTypes::RenderUnit::RenderMapUnits );
symbolLayer->setUseCustomDashPattern( true );
QgsLineSymbol *symbol = new QgsLineSymbol();
symbol->changeSymbolLayer( 0, symbolLayer.release() );
std::unique_ptr< QgsVectorLayer > vl = qgis::make_unique< QgsVectorLayer >( QStringLiteral( "CompoundCurve?crs=epsg:2056" ), QString(), QStringLiteral( "memory" ) );
QgsGeometry g = QgsGeometry::fromWkt( "CompoundCurve ((2689563.84200000017881393 1283531.23699999996460974, 2689563.42499999981373549 1283537.55499999993480742, 2689563.19900000002235174 1283540.52399999997578561, 2689562.99800000013783574 1283543.42999999993480742, 2689562.66900000022724271 1283548.56000000005587935, 2689562.43399999989196658 1283555.287999999942258))" );
QgsFeature f;
f.setGeometry( g );
vl->dataProvider()->addFeatures( QgsFeatureList() << f );
QgsSingleSymbolRenderer *renderer = new QgsSingleSymbolRenderer( symbol );
vl->setRenderer( renderer );
QgsDxfExport d;
d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( vl.get() ) );
d.setSymbologyExport( QgsDxfExport::SymbologyExport::SymbolLayerSymbology );
QgsMapSettings mapSettings;
QSize size( 640, 480 );
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl.get() );
mapSettings.setOutputDpi( 96 );
mapSettings.setDestinationCrs( vl->crs() );
d.setMapSettings( mapSettings );
d.setSymbologyScale( 1000 );
QString file = getTempFileName( "dashed_line_dxf" );
QFile dxfFile( file );
QCOMPARE( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success );
dxfFile.close();
QString debugInfo;
// Make sure the style definition for the dashed line is there
QVERIFY2( fileContainsText( file,
"LTYPE\n"
" 5\n"
"6c\n"
"100\n"
"AcDbSymbolTableRecord\n"
"100\n"
"AcDbLinetypeTableRecord\n"
" 2\n"
"symbolLayer0\n"
" 70\n"
" 64\n"
" 3\n"
"\n"
" 72\n"
" 65\n"
" 73\n"
" 2\n"
" 40\n"
"REGEX ^0\\.8[0-9]*\n"
" 49\n"
"0.5\n"
" 74\n"
" 0\n"
" 49\n"
"REGEX ^-0\\.3[0-9]*\n"
" 74\n"
" 0", &debugInfo ), debugInfo.toUtf8().constData() );
// Make sure that the polyline references the style symbolLayer0
QVERIFY2( fileContainsText( file,
"LWPOLYLINE\n"
" 5\n"
"83\n"
" 8\n"
"0\n"
"100\n"
"AcDbEntity\n"
"100\n"
"AcDbPolyline\n"
" 6\n"
"symbolLayer0\n"
"420\n"
" 0\n"
" 90\n"
" 6\n"
" 70\n"
" 0\n"
" 43\n"
"0.11\n"
" 10\n"
"REGEX ^2689563.84[0-9]*\n"
" 20\n"
"REGEX ^1283531.23[0-9]*\n"
" 10\n"
"REGEX ^2689563.42[0-9]*\n"
" 20\n"
"REGEX ^1283537.55[0-9]*\n"
" 10\n"
"REGEX ^2689563.19[0-9]*\n"
" 20\n"
"REGEX ^1283540.52[0-9]*\n"
" 10\n"
"REGEX ^2689562.99[0-9]*\n"
" 20\n"
"REGEX ^1283543.42[0-9]*\n"
" 10\n"
"REGEX ^2689562.66[0-9]*\n"
" 20\n"
"REGEX ^1283548.56[0-9]*\n"
" 10\n"
"REGEX ^2689562.43[0-9]*\n"
" 20\n"
"REGEX ^1283555.28[0-9]*\n"
" 0\n"
"ENDSEC"
, &debugInfo ), debugInfo.toUtf8().constData() );
}
bool TestQgsDxfExport::fileContainsText( const QString &path, const QString &text, QString *debugInfo ) const
{
QStringList debugLines;

View File

@ -86,13 +86,13 @@ void TestQgsServerWmsDxf::use_title_as_layername_true()
context.setParameters( parameters );
QgsWms::QgsRenderer renderer( context );
QgsDxfExport exporter = renderer.getDxf();
std::unique_ptr<QgsDxfExport> exporter = renderer.getDxf();
const QString name = exporter.layerName( vl );
QCOMPARE( exporter.layerName( vl ), QString( "testlayer \u00E8\u00E9" ) );
const QString name = exporter->layerName( vl );
QCOMPARE( exporter->layerName( vl ), QString( "testlayer \u00E8\u00E9" ) );
const QgsFeature ft = vl->getFeature( 1 );
QCOMPARE( exporter.layerName( vl->id(), ft ), QString( "two" ) );
QCOMPARE( exporter->layerName( vl->id(), ft ), QString( "two" ) );
}
void TestQgsServerWmsDxf::use_title_as_layername_false()
@ -135,13 +135,13 @@ void TestQgsServerWmsDxf::use_title_as_layername_false()
context.setParameters( parameters );
QgsWms::QgsRenderer renderer( context );
QgsDxfExport exporter = renderer.getDxf();
std::unique_ptr<QgsDxfExport> exporter = renderer.getDxf();
const QString name = exporter.layerName( vl );
QCOMPARE( exporter.layerName( vl ), QString( "A test vector layer" ) );
const QString name = exporter->layerName( vl );
QCOMPARE( exporter->layerName( vl ), QString( "A test vector layer" ) );
const QgsFeature ft = vl->getFeature( 1 );
QCOMPARE( exporter.layerName( vl->id(), ft ), QString( "A test vector layer" ) );
QCOMPARE( exporter->layerName( vl->id(), ft ), QString( "A test vector layer" ) );
}
QGSTEST_MAIN( TestQgsServerWmsDxf )