• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 23089271999

14 Mar 2026 01:51PM UTC coverage: 34.05% (+0.08%) from 33.973%
23089271999

push

github

mcallegari
Enter 5.2.1 release

15814 of 46444 relevant lines covered (34.05%)

22891.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.44
/engine/src/avolitesd4parser.cpp
1
/*
2
  Q Light Controller Plus
3
  avolitesd4parser.cpp
4

5
  Copyright (C) Rui Barreiros
6
                Heikki Junnila
7
                Massimo Callegari
8

9
  Licensed under the Apache License, Version 2.0 (the "License");
10
  you may not use this file except in compliance with the License.
11
  You may obtain a copy of the License at
12

13
      http://www.apache.org/licenses/LICENSE-2.0.txt
14

15
  Unless required by applicable law or agreed to in writing, software
16
  distributed under the License is distributed on an "AS IS" BASIS,
17
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
  See the License for the specific language governing permissions and
19
  limitations under the License.
20
*/
21

22
#include <QXmlStreamReader>
23
#include <QStringList>
24
#include <QDebug>
25

26
#include "avolitesd4parser.h"
27
#include "qlcfixturemode.h"
28
#include "qlcfixturedef.h"
29
#include "qlccapability.h"
30
#include "qlcphysical.h"
31
#include "qlcchannel.h"
32
#include "qlcfile.h"
33

34
// Channel groups
35
#define KD4GroupSpecial   QStringLiteral("S")
36
#define KD4GroupIntensity QStringLiteral("I")
37
#define KD4GroupPanTilt   QStringLiteral("P")
38
#define KD4GroupColour    QStringLiteral("C")
39
#define KD4GroupGobo      QStringLiteral("G")
40
#define KD4GroupBeam      QStringLiteral("B")
41
#define KD4GroupEffect    QStringLiteral("E")
42

43
// Channels
44
#define KD4TagFixture   QStringLiteral("Fixture")
45
#define KD4TagName      QStringLiteral("Name")
46
#define KD4TagShortName QStringLiteral("ShortName")
47
#define KD4TagCompany   QStringLiteral("Company")
48
#define KD4TagControl   QStringLiteral("Control")
49
#define KD4TagID        QStringLiteral("ID")
50
#define KD4TagGroup     QStringLiteral("Group")
51
#define KD4TagSpeed     QStringLiteral("Speed")
52
#define KD4TagMacro     QStringLiteral("Macro")
53
#define KD4TagReserved  QStringLiteral("Reserved")
54
#define KD4TagShutter   QStringLiteral("Shutter")
55
#define KD4TagPan       QStringLiteral("Pan")
56
#define KD4TagTilt      QStringLiteral("Tilt")
57
#define KD4TagCyan      QStringLiteral("Cyan")
58
#define KD4TagMagenta   QStringLiteral("Magenta")
59
#define KD4TagYellow    QStringLiteral("Yellow")
60
#define KD4TagRed       QStringLiteral("Red")
61
#define KD4TagBlue      QStringLiteral("Blue")
62
#define KD4TagGreen     QStringLiteral("Green")
63
#define KD4TagPrism     QStringLiteral("Prism")
64
#define KD4TagEffect    QStringLiteral("Effect")
65
#define KD4TagAttribute QStringLiteral("Attribute")
66
#define KD4TagUpdate    QStringLiteral("Update")
67

68
// Capabilities
69
#define KD4TagFunction                  QStringLiteral("Function")
70
#define KD4TagFunctionName              QStringLiteral("Name")
71
#define KD4TagFunctionDmx               QStringLiteral("Dmx")
72
#define KD4TagFunctionDmxValueSeparator QStringLiteral("~")
73

74
// Mode section
75
#define KD4TagMode                 QStringLiteral("Mode")
76
#define KD4TagModeName             QStringLiteral("Name")
77
#define KD4TagModeInclude          QStringLiteral("Include")
78
#define KD4TagModeAttribute        QStringLiteral("Attribute")
79
#define KD4TagModeChannelOffset    QStringLiteral("ChannelOffset")
80
#define KD4TagModeID               QStringLiteral("ID")
81
#define KD4TagModeChannelSeparator QStringLiteral(",")
82

83
// Palettes section
84
#define KD4TagPalettes          QStringLiteral("Palettes")
85
#define KD4TagPalette           QStringLiteral("Palette")
86

87
// Physical section
88
#define KD4TagPhysical                     QStringLiteral("Physical")
89
#define KD4TagPhysicalBulb                 QStringLiteral("Bulb")
90
#define KD4TagPhysicalBulbType             QStringLiteral("Type")
91
#define KD4TagPhysicalBulbLumens           QStringLiteral("Lumens")
92
#define KD4TagPhysicalBulbColourTemp       QStringLiteral("ColourTemp")
93
#define KD4TagPhysicalLens                 QStringLiteral("Lens")
94
#define KD4TagPhysicalLensName             QStringLiteral("Name")
95
#define KD4TagPhysicalLensDegrees          QStringLiteral("Degrees")
96
#define KD4TagPhysicalLensDegreesSeparator QStringLiteral("~")
97
#define KD4TagPhysicalWeight               QStringLiteral("Weight")
98
#define KD4TagPhysicalWeightKg             QStringLiteral("Kg")
99
#define KD4TagPhysicalSize                 QStringLiteral("Size")
100
#define KD4TagPhysicalSizeHeight           QStringLiteral("Height")
101
#define KD4TagPhysicalSizeWidth            QStringLiteral("Width")
102
#define KD4TagPhysicalSizeDepth            QStringLiteral("Depth")
103
#define KD4TagPhysicalFocus                QStringLiteral("Focus")
104
#define KD4TagPhysicalFocusType            QStringLiteral("Type")
105
#define KD4TagPhysicalFocusPanMax          QStringLiteral("PanMax")
106
#define KD4TagPhysicalFocusTiltMax         QStringLiteral("TiltMax")
107

108
// Static attibute map shared between instances of the parser, initialized only
109
// once per application.
110
AvolitesD4Parser::StringToEnumMap AvolitesD4Parser::s_attributesMap;
111

112
AvolitesD4Parser::AvolitesD4Parser()
1✔
113
{
114
    if (s_attributesMap.isEmpty() == true)
×
115
    {
116
        // Setup our attribute mapping map helper
117
        s_attributesMap.insert(KD4GroupSpecial, AvolitesD4Parser::SPECIAL);
2✔
118
        s_attributesMap.insert(KD4GroupIntensity, AvolitesD4Parser::INTENSITY);
2✔
119
        s_attributesMap.insert(KD4GroupPanTilt, AvolitesD4Parser::PANTILT);
2✔
120
        s_attributesMap.insert(KD4GroupColour, AvolitesD4Parser::COLOUR);
2✔
121
        s_attributesMap.insert(KD4GroupGobo, AvolitesD4Parser::GOBO);
2✔
122
        s_attributesMap.insert(KD4GroupBeam, AvolitesD4Parser::BEAM);
2✔
123
        s_attributesMap.insert(KD4GroupEffect, AvolitesD4Parser::EFFECT);
2✔
124
    }
125
}
1✔
126

127
AvolitesD4Parser::~AvolitesD4Parser()
1✔
128
{
129
}
1✔
130

131
bool AvolitesD4Parser::loadXML(const QString& path, QLCFixtureDef *fixtureDef)
1✔
132
{
133
    m_lastError = QString();
1✔
134
    m_channels.clear();
1✔
135

136
    if (path.isEmpty())
1✔
137
    {
138
        m_lastError = "filename not specified";
×
139
        return false;
×
140
    }
141

142
    QXmlStreamReader *doc = QLCFile::getXMLReader(path);
1✔
143
    if (doc == NULL || doc->device() == NULL || doc->hasError())
1✔
144
    {
145
        m_lastError = QString("Unable to read from %1").arg(path);
2✔
146
        return false;
1✔
147
    }
148

149
    // check if the document has <Fixture></Fixture> if not then it's not a valid file
150
    if (doc->readNextStartElement() == false || doc->name() != KD4TagFixture)
×
151
    {
152
        m_lastError = "wrong document format";
×
153
        return false;
×
154
    }
155

156
    QXmlStreamAttributes attrs = doc->attributes();
×
157
    if ((!attrs.hasAttribute(KD4TagName)) || (!attrs.hasAttribute(KD4TagCompany)))
×
158
    {
159
        m_lastError = "the document doesn't have the required attributes";
×
160
        return false;
161
    }
162

163
    fixtureDef->setManufacturer(doc->attributes().value(KD4TagCompany).toString());
×
164
    fixtureDef->setModel(doc->attributes().value(KD4TagName).toString());
×
165
    fixtureDef->setAuthor("Avolites");
×
166

167
    while (doc->readNextStartElement())
×
168
    {
169
        if (doc->name() == KD4TagControl)
×
170
        {
171
            // Parse a channel
172
            if (parseChannel(doc, fixtureDef) == false)
×
173
                return false;
174
        }
175
        else if (doc->name() == KD4TagMode)
×
176
        {
177
            // Parse mode tag
178
            parseMode(doc, fixtureDef);
×
179
        }
180
        else if (doc->name() == KD4TagPalettes)
×
181
        {
182
            // TODO TODO TODO
183
            // Maybe also import preset palettes and macros ?!?!?!?!
184
            /**
185
                Can't be done for now, as qxf files don't have any information on preset palettes or macros
186
                for fixtures, they are automatically generated on the main application maybe in future... **/
187
            doc->skipCurrentElement();
×
188
        }
189
        else
190
        {
191
            qWarning() << Q_FUNC_INFO << "Unknown D4 tag:" << doc->name().toString();
×
192
            doc->skipCurrentElement();
×
193
        }
194
    }
195

196
    fixtureDef->setType(guessType(fixtureDef));
×
197

198
    QLCFile::releaseXMLReader(doc);
×
199

200
    return true;
201
}
202

203
QString AvolitesD4Parser::lastError() const
1✔
204
{
205
    return m_lastError;
1✔
206
}
207

208
QLCChannel::Group AvolitesD4Parser::getGroup(const QString& ID, const QString& name, const QString& group) const
×
209
{
210
    if (name.isEmpty() && group.isEmpty())
×
211
        return QLCChannel::NoGroup;
212

213
    switch (stringToAttributeEnum(group))
×
214
    {
215
        case AvolitesD4Parser::SPECIAL:
×
216
            if (ID.contains(KD4TagSpeed, Qt::CaseInsensitive) ||
×
217
                name.contains(KD4TagSpeed, Qt::CaseInsensitive))
×
218
                    return QLCChannel::Speed;
219
            else if (ID.contains(KD4TagMacro, Qt::CaseInsensitive) ||
×
220
                     name.contains(KD4TagMacro, Qt::CaseInsensitive))
×
221
                        return QLCChannel::Effect;
222
            else if (ID.contains(KD4TagReserved, Qt::CaseInsensitive) ||
×
223
                     name.contains(KD4TagReserved, Qt::CaseInsensitive))
×
224
                        return QLCChannel::NoGroup;
225
            else
226
                return QLCChannel::Maintenance;
×
227
        break;
228

229
        default:
×
230
        case AvolitesD4Parser::INTENSITY:
231
            if (ID.contains(KD4TagShutter, Qt::CaseInsensitive) ||
×
232
                name.contains(KD4TagShutter, Qt::CaseInsensitive))
×
233
                    return QLCChannel::Shutter;
234
            else
235
                return QLCChannel::Intensity;
×
236
        break;
237

238
        case AvolitesD4Parser::PANTILT:
×
239
            if (ID.contains(KD4TagPan, Qt::CaseInsensitive) ||
×
240
                name.contains(KD4TagPan, Qt::CaseInsensitive))
×
241
                    return QLCChannel::Pan;
242
            else if (ID.contains(KD4TagTilt, Qt::CaseInsensitive) ||
×
243
                    name.contains(KD4TagTilt, Qt::CaseInsensitive))
×
244
                        return QLCChannel::Tilt;
245
            else
246
                return QLCChannel::NoGroup;
×
247
        break;
248

249
        case AvolitesD4Parser::COLOUR:
×
250
            if (ID.contains(KD4TagCyan, Qt::CaseInsensitive) ||
×
251
                name.contains(KD4TagCyan, Qt::CaseInsensitive))
×
252
                    return QLCChannel::Intensity;
253
            else if (ID.contains(KD4TagMagenta, Qt::CaseInsensitive) ||
×
254
                    name.contains(KD4TagMagenta, Qt::CaseInsensitive))
×
255
                        return QLCChannel::Intensity;
256
            else if (ID.contains(KD4TagYellow, Qt::CaseInsensitive) ||
×
257
                    name.contains(KD4TagYellow, Qt::CaseInsensitive))
×
258
                        return QLCChannel::Intensity;
259
            else if (ID.contains(KD4TagRed, Qt::CaseInsensitive) ||
×
260
                    name.contains(KD4TagRed, Qt::CaseInsensitive))
×
261
                        return QLCChannel::Intensity;
262
            else if (ID.contains(KD4TagGreen, Qt::CaseInsensitive) ||
×
263
                    name.contains(KD4TagGreen, Qt::CaseInsensitive))
×
264
                        return QLCChannel::Intensity;
265
            else if (ID.contains(KD4TagBlue, Qt::CaseInsensitive) ||
×
266
                    name.contains(KD4TagBlue, Qt::CaseInsensitive))
×
267
                        return QLCChannel::Intensity;
268
            else
269
                return QLCChannel::Colour;
×
270
        break;
271

272
        case AvolitesD4Parser::GOBO:
273
            return QLCChannel::Gobo;
274
        break;
275

276
        case AvolitesD4Parser::BEAM:
×
277
            return QLCChannel::Beam;
×
278
        break;
279

280
        case AvolitesD4Parser::EFFECT:
×
281
            if (ID.contains(KD4TagPrism, Qt::CaseInsensitive) ||
×
282
                name.contains(KD4TagPrism, Qt::CaseInsensitive))
×
283
                    return QLCChannel::Prism;
284
            else if (ID.contains(KD4TagEffect, Qt::CaseInsensitive) ||
×
285
                     name.contains(KD4TagEffect, Qt::CaseInsensitive))
×
286
                        return QLCChannel::Effect;
287
            else if (ID.contains(KD4TagMacro, Qt::CaseInsensitive) ||
×
288
                     name.contains(KD4TagMacro, Qt::CaseInsensitive))
×
289
                        return QLCChannel::Effect;
290
            else
291
                return QLCChannel::NoGroup;
×
292
        break;
293
    }
294

295
    return QLCChannel::NoGroup;
296
}
297

298
QLCChannel::PrimaryColour AvolitesD4Parser::getColour(const QString& ID, const QString& name, const QString& group) const
×
299
{
300
    if (group.compare(KD4GroupColour, Qt::CaseInsensitive) != 0)
×
301
        return QLCChannel::NoColour;
302

303
    if (ID.contains(KD4TagCyan, Qt::CaseInsensitive) ||
×
304
        name.contains(KD4TagCyan, Qt::CaseInsensitive))
×
305
            return QLCChannel::Cyan;
306
    else if (ID.contains(KD4TagMagenta, Qt::CaseInsensitive) ||
×
307
            name.contains(KD4TagMagenta, Qt::CaseInsensitive))
×
308
                return QLCChannel::Magenta;
309
    else if (ID.contains(KD4TagYellow, Qt::CaseInsensitive) ||
×
310
             name.contains(KD4TagYellow, Qt::CaseInsensitive))
×
311
                return QLCChannel::Yellow;
312
    else if (ID.contains(KD4TagRed, Qt::CaseInsensitive) ||
×
313
            name.contains(KD4TagRed, Qt::CaseInsensitive))
×
314
                return QLCChannel::Red;
315
    else if (ID.contains(KD4TagGreen, Qt::CaseInsensitive) ||
×
316
            name.contains(KD4TagGreen, Qt::CaseInsensitive))
×
317
                return QLCChannel::Green;
318
    else if (ID.contains(KD4TagBlue, Qt::CaseInsensitive) ||
×
319
            name.contains(KD4TagBlue, Qt::CaseInsensitive))
×
320
                return QLCChannel::Blue;
321
    else
322
        return QLCChannel::NoColour;
×
323
}
324
/*
325
bool AvolitesD4Parser::isFunction(const QDomElement& elem) const
326
{
327
    QDomElement el = elem.firstChildElement(KD4TagFunction);
328
    for (; !el.isNull(); el = el.nextSiblingElement(KD4TagFunction))
329
    {
330
        if (!el.attribute(KD4TagUpdate).isEmpty())
331
            return true;
332
    }
333

334
    return false;
335
}
336
*/
337
bool AvolitesD4Parser::is16Bit(const QString& dmx) const
×
338
{
339
    QStringList dmxValues = dmx.split(KD4TagFunctionDmxValueSeparator);
×
340

341
    if (dmxValues.isEmpty())
×
342
        return false;
343

344
    // I remember avolites sometimes switches the sides on dmx values, sometimes, the left of the ~
345
    // is not always the lowest, better check both sides
346

347
    if (dmxValues.value(0).toInt() > 256)
×
348
        return true;
349

350
    // Is there a right side ? (there should always be something in the right side of the ~,
351
    // or avolites desks won't parse the file, anyway, there should be a check, if some smart
352
    // dude will complain this crashes with his D4 file)
353

354
    if (dmxValues.size() > 1)
×
355
    {
356
        if (dmxValues.value(1).toInt() > 256)
×
357
            return true;
×
358
    }
359

360
    return false;
361
}
362

363
QLCCapability *AvolitesD4Parser::getCapability(const QString& dmx, const QString& name, bool isFine) const
×
364
{
365
    if (dmx.isEmpty())
×
366
        return NULL;
367

368
    QStringList dmxValues = dmx.split(KD4TagFunctionDmxValueSeparator);
×
369

370
    // Here, instead of checking all the time for both dmxValues, it's more efficient to
371
    // set a default value if it's missing
372
    if (dmxValues.size() == 0)
×
373
        dmxValues << QString("0") << QString("0");
×
374
    else if (dmxValues.size() == 1)
×
375
        dmxValues << QString("0");
×
376

377
    // if were trying to get capabilities from a 16bit channel, we need to change them to 8 bit
378
    int minValue = 0, maxValue = 0;
379

380
    if (dmxValues.value(0).toInt() > 256)
×
381
        minValue = 0xFF & (dmxValues.value(0).toInt() >> 8);
×
382
    else
383
        minValue = dmxValues.value(0).toInt();
×
384

385
    if (dmxValues.value(1).toInt() > 256)
×
386
        maxValue = 0xFF & (dmxValues.value(1).toInt() >> 8);
×
387
    else
388
        maxValue = dmxValues.value(1).toInt();
×
389

390
    // Guess what, I seen this happen, it seems min value is not always on the left of the ~
391
    // sometimes they're switched!
392
    if (minValue > maxValue)
×
393
    {
394
        int tmp = maxValue;
395
        maxValue = minValue;
396
        minValue = tmp;
397
    }
398

399
    QLCCapability *cap = new QLCCapability(minValue, maxValue, isFine ? (name + " Fine") : name);
×
400

401
    return cap;
402
}
403

404
bool AvolitesD4Parser::parseChannel(QXmlStreamReader *doc, QLCFixtureDef *fixtureDef)
×
405
{
406
    if (doc->name() != KD4TagControl)
×
407
        return false;
408

409
    while (doc->readNextStartElement())
×
410
    {
411
        if (doc->name() == KD4TagAttribute)
×
412
        {
413
            QString ID = doc->attributes().value(KD4TagID).toString();
×
414
            if (ID.isEmpty())
×
415
            {
416
                doc->skipCurrentElement();
×
417
                continue;
418
            }
419

420
            parseAttribute(doc, fixtureDef);
×
421
        }
422
        else
423
        {
424
            qWarning() << Q_FUNC_INFO << "Unknown control tag:" << doc->name().toString();
×
425
            doc->skipCurrentElement();
×
426
        }
427
    }
428

429
    return true;
430
}
431

432
bool AvolitesD4Parser::parseFunction(QXmlStreamReader *doc, QLCFixtureDef *fixtureDef,
×
433
                                     QLCChannel *channel, QString ID, QString group)
434
{
435
    QXmlStreamAttributes attrs = doc->attributes();
×
436
    QString name = attrs.value(KD4TagFunctionName).toString();
×
437
    if (name.isEmpty())
×
438
    {
439
        doc->skipCurrentElement();
×
440
        return true;
441
    }
442

443
    QString dmx = attrs.value(KD4TagFunctionDmx).toString();
×
444
    QLCCapability *cap = getCapability(dmx, name);
×
445

446
    if (cap != NULL)
×
447
    {
448
        // We just ignore capability adding errors, because avolites often repeats attributes due to conditionals
449
        // so we just add the first one we get, the repeating ones are ignored naturally and
450
        // obviously further human verification is needed on the fixture definition to fix this issues
451
        channel->addCapability(cap);
×
452
    }
453

454
    if (is16Bit(dmx))
×
455
    {
456
        QLCChannel *fineChan = new QLCChannel();
×
457
        fineChan->setName(name + " Fine");
×
458
        fineChan->setGroup(getGroup(ID, name, group));
×
459
        fineChan->setColour(getColour(ID, name, group));
×
460
        fineChan->setControlByte(QLCChannel::LSB);
×
461
        QLCCapability *fineCap = getCapability(dmx, name, true);
×
462
        if (fineCap != NULL)
×
463
            fineChan->addCapability(fineCap);
×
464
        fixtureDef->addChannel(fineChan);
×
465
        m_channels.insert(ID + " Fine", fineChan);
×
466
    }
467
    //qDebug() << "Capability found" << cap->name() << cap->min() << cap->max();
468
    doc->skipCurrentElement();
×
469

470
    return true;
471
}
472

473
bool AvolitesD4Parser::parseAttribute(QXmlStreamReader *doc, QLCFixtureDef *fixtureDef)
×
474
{
475
    if (doc->name() != KD4TagAttribute)
×
476
        return false;
477

478
    QXmlStreamAttributes attrs = doc->attributes();
×
479
    QString ID = doc->attributes().value(KD4TagID).toString();
×
480
    QString name = attrs.value(KD4TagName).toString();
×
481
    QString group = attrs.value(KD4TagGroup).toString();
×
482

483
    QLCChannel *chan = new QLCChannel();
×
484
    chan->setName(name);
×
485
    chan->setGroup(getGroup(ID, name, group));
×
486
    chan->setColour(getColour(ID, name, group));
×
487
    chan->setControlByte(QLCChannel::MSB);
×
488

489
    // add channel to fixture definition
490
    fixtureDef->addChannel(chan);
×
491
    m_channels.insert(ID, chan);
×
492

493
    // if this channel is a NoGroup then we don't need to continue
494
    // no capabilities nor 16 bit channel
495
    if (chan->group() == QLCChannel::NoGroup)
×
496
    {
497
        doc->skipCurrentElement();
×
498
        return true;
499
    }
500

501
    while (doc->readNextStartElement())
×
502
    {
503
        if (doc->name() == KD4TagFunction)
×
504
        {
505
            parseFunction(doc, fixtureDef, chan, ID, group);
×
506
        }
507
        else
508
        {
509
            qWarning() << Q_FUNC_INFO << "Unknown attribute tag:" << doc->name().toString();
×
510
            doc->skipCurrentElement();
×
511
        }
512
    }
513
    chan->sortCapabilities();
×
514

515
    return true;
516
}
517

518
bool AvolitesD4Parser::parseMode(QXmlStreamReader *doc, QLCFixtureDef *fixtureDef) const
×
519
{
520
    if (doc->name() != KD4TagMode)
×
521
        return false;
522

523
    QString name = doc->attributes().value(KD4TagModeName).toString();
×
524

525
    if (name.isEmpty())
×
526
        return false;
527

528
    QLCFixtureMode *mode = new QLCFixtureMode(fixtureDef);
×
529
    mode->setName(name);
×
530

531
    while (doc->readNextStartElement())
×
532
    {
533
        if (doc->name() == KD4TagModeInclude)
×
534
        {
535
            parseInclude(doc, mode);
×
536
        }
537
        else if (doc->name() == KD4TagPhysical)
×
538
        {
539
            // Parse physical
540
            parsePhysical(doc, fixtureDef, mode);
×
541
        }
542
        else
543
        {
544
            qWarning() << Q_FUNC_INFO << "Unknown mode tag:" << doc->name().toString();
×
545
            doc->skipCurrentElement();
×
546
        }
547
    }
548

549
    // Add the mode
550
    fixtureDef->addMode(mode);
×
551

552
    return true;
553
}
554

555
bool AvolitesD4Parser::comparePhysical(const QLCPhysical &globalPhy, const QLCPhysical &modePhy) const
×
556
{
557
    if (globalPhy.isEmpty())
×
558
        return true;
559

560
    if (globalPhy.bulbLumens() != modePhy.bulbLumens() ||
×
561
        globalPhy.bulbColourTemperature() != modePhy.bulbColourTemperature() ||
×
562
        globalPhy.weight() != modePhy.weight() ||
×
563
        globalPhy.width() != modePhy.width() ||
×
564
        globalPhy.height() != modePhy.height() ||
×
565
        globalPhy.depth() != modePhy.depth() ||
×
566
        globalPhy.lensDegreesMin() != modePhy.lensDegreesMin() ||
×
567
        globalPhy.lensDegreesMax() != modePhy.lensDegreesMax() ||
×
568
        globalPhy.focusPanMax() != modePhy.focusPanMax() ||
×
569
        globalPhy.focusTiltMax() != modePhy.focusTiltMax() ||
×
570
        globalPhy.powerConsumption() != modePhy.powerConsumption())
×
571
            return false;
×
572

573
    return true;
574
}
575

576
void AvolitesD4Parser::parsePhysical(QXmlStreamReader *doc, QLCFixtureDef *fixtureDef, QLCFixtureMode *mode) const
×
577
{
578
    if (doc->name() != KD4TagPhysical)
×
579
        return;
×
580

581
    QLCPhysical phys;
×
582

583
    while (doc->readNextStartElement())
×
584
    {
585
        QXmlStreamAttributes attrs = doc->attributes();
×
586

587
        if (doc->name() == KD4TagPhysicalBulb)
×
588
        {
589
            phys.setBulbType(attrs.value(KD4TagPhysicalBulbType).toString());
×
590
            phys.setBulbLumens(attrs.value(KD4TagPhysicalBulbLumens).toString().toInt());
×
591
            // this is kind of wrong cause a ColourTemp tag can be "0, 0, 0"
592
            phys.setBulbColourTemperature(attrs.value(KD4TagPhysicalBulbColourTemp).toString().toInt());
×
593
        }
594
        else if (doc->name() == KD4TagPhysicalLens)
×
595
        {
596
            phys.setLensName(attrs.value(KD4TagName).toString());
×
597

598
            QString degrees = attrs.value(KD4TagPhysicalLensDegrees).toString();
×
599
            if (degrees.contains(KD4TagPhysicalLensDegreesSeparator))
×
600
            {
601
                QStringList deg = degrees.split(KD4TagPhysicalLensDegreesSeparator);
×
602
                if (deg.size() == 2)
×
603
                {
604
                    if (deg.value(0).toInt() > deg.value(1).toInt())
×
605
                    {
606
                        phys.setLensDegreesMin(deg.value(1).toInt());
×
607
                        phys.setLensDegreesMax(deg.value(0).toInt());
×
608
                    }
609
                    else
610
                    {
611
                        phys.setLensDegreesMin(deg.value(0).toInt());
×
612
                        phys.setLensDegreesMax(deg.value(1).toInt());
×
613
                    }
614
                } else if (deg.size() == 1)
×
615
                {
616
                    phys.setLensDegreesMax(deg.value(0).toInt());
×
617
                    phys.setLensDegreesMin(deg.value(0).toInt());
×
618
                }
619
            }
620
            else if (!degrees.isEmpty())
×
621
            {
622
                phys.setLensDegreesMax(degrees.toInt());
×
623
                phys.setLensDegreesMin(degrees.toInt());
×
624
            }
625
        }
626
        else if (doc->name() == KD4TagPhysicalWeight)
×
627
        {
628
            phys.setWeight(attrs.value(KD4TagPhysicalWeightKg).toString().toDouble());
×
629
        }
630
        else if (doc->name() == KD4TagPhysicalSize)
×
631
        {
632
            phys.setHeight(attrs.value(KD4TagPhysicalSizeHeight).toString().toDouble() * 1000);
×
633
            phys.setWidth(attrs.value(KD4TagPhysicalSizeWidth).toString().toDouble() * 1000);
×
634
            phys.setDepth(attrs.value(KD4TagPhysicalSizeDepth).toString().toDouble() * 1000);
×
635
        }
636
        else if (doc->name() == KD4TagPhysicalFocus)
×
637
        {
638
            phys.setFocusType(attrs.value(KD4TagPhysicalFocusType).toString());
×
639
            phys.setFocusPanMax(attrs.value(KD4TagPhysicalFocusPanMax).toString().toInt());
×
640
            phys.setFocusTiltMax(attrs.value(KD4TagPhysicalFocusTiltMax).toString().toInt());
×
641
        }
642
        else
643
        {
644
            qWarning() << Q_FUNC_INFO << "Unknown physical tag:" << doc->name().toString();
×
645
        }
646
        doc->skipCurrentElement();
×
647
    }
648

649
    if (comparePhysical(fixtureDef->physical(), phys) == true)
×
650
        fixtureDef->setPhysical(phys);
×
651
    else
652
        mode->setPhysical(phys);
×
653
}
×
654

655
void AvolitesD4Parser::parseInclude(QXmlStreamReader *doc, QLCFixtureMode *mode) const
×
656
{
657
    if (doc->name() != KD4TagModeInclude)
×
658
        return;
×
659

660
    QMap <int, QLCChannel*> channelList;
×
661

662
    // loop through Attribute tags
663
    while (doc->readNextStartElement())
×
664
    {
665
        if (doc->name() == KD4TagModeAttribute)
×
666
        {
667
            QXmlStreamAttributes attrs = doc->attributes();
×
668
            // Some channels are conditionals not real channels
669
            if (attrs.value(KD4TagModeChannelOffset).toString().isEmpty())
×
670
            {
671
                doc->skipCurrentElement();
×
672
                continue;
×
673
            }
674

675
            QString modeID = attrs.value(KD4TagModeID).toString();
×
676
            if (m_channels.contains(modeID))
×
677
            {
678
                // might be a 16 bit channel, so we have 2 offsets
679
                QString offset = attrs.value(KD4TagModeChannelOffset).toString();
×
680
                if (offset.contains(KD4TagModeChannelSeparator, Qt::CaseInsensitive))
×
681
                {
682
                    // 16 bit address, we need to add 2 channels, this one, and we need the fine one
683
                    QStringList offsetValues = offset.split(KD4TagModeChannelSeparator);
×
684
                    // if there's more than 2 addresses, or less than 2, bail out, don't know how to handle this, shouldn't happen ever.
685
                    if (offsetValues.size() > 2 || offsetValues.size() < 2)
×
686
                        continue;
687

688
                    // Add this one
689
                    channelList.insert(offsetValues.value(0).toInt(), m_channels.value(modeID));
×
690
                    QString name = m_channels.value(modeID)->name();
×
691

692
                    // Search for the fine one
693
                    QMapIterator <QString,QLCChannel*> it(m_channels);
×
694
                    while (it.hasNext() == true)
×
695
                    {
696
                        it.next();
697
                        QLCChannel *ch(it.value());
×
698
                        Q_ASSERT(ch != NULL);
699

700
                        if (ch->name() == QString(name + " Fine"))
×
701
                            channelList.insert(offsetValues.value(1).toInt(), ch);
×
702
                    }
703
                }
704
                else
705
                {
706
                    channelList.insert(offset.toInt(), m_channels.value(modeID));
×
707
                }
708
            }
709
        }
710
        else
711
        {
712
            qWarning() << Q_FUNC_INFO << "Unknown include tag:" << doc->name().toString();
×
713
        }
714
        doc->skipCurrentElement();
×
715
    }
716

717
    QMapIterator <int,QLCChannel*> it(channelList);
×
718
    while (it.hasNext() == true)
×
719
    {
720
        it.next();
721
        Q_ASSERT(mode != NULL);
722
        mode->insertChannel(it.value(), it.key());
×
723
    }
724
}
725

726
AvolitesD4Parser::Attributes AvolitesD4Parser::stringToAttributeEnum(const QString& attr) const
×
727
{
728
    // If there is none, empty or whatever always return something, default is SPECIAL
729
    if (attr.isEmpty())
×
730
        return AvolitesD4Parser::SPECIAL;
731

732
    if (s_attributesMap.value(attr.toUpper()))
×
733
        return s_attributesMap.value(attr.toUpper());
×
734
    else
735
        return AvolitesD4Parser::SPECIAL;
736
}
737

738
QLCFixtureDef::FixtureType AvolitesD4Parser::guessType(const QLCFixtureDef *def) const
×
739
{
740
    Q_ASSERT(def != NULL);
741

742
    int pan = 0, tilt = 0;
743
    int r = 0, g = 0, b = 0, c = 0, m = 0, y = 0, nocol = 0;
744
    int gobo = 0, colour = 0;
745
    int haze = 0, smoke = 0;
746
    int strobe = 0;
747

748
    QListIterator <QLCChannel*> it(def->channels());
×
749
    while (it.hasNext() == true)
×
750
    {
751
        const QLCChannel *ch(it.next());
×
752
        if (ch->group() == QLCChannel::Pan)
×
753
        {
754
            pan++;
×
755
        }
756
        else if (ch->group() == QLCChannel::Tilt)
×
757
        {
758
            tilt++;
×
759
        }
760
        else if (ch->group() == QLCChannel::Intensity)
×
761
        {
762
            if (ch->colour() == QLCChannel::Red)
×
763
                r++;
×
764
            else if (ch->colour() == QLCChannel::Green)
×
765
                g++;
×
766
            else if (ch->colour() == QLCChannel::Blue)
×
767
                b++;
×
768
            else if (ch->colour() == QLCChannel::Cyan)
×
769
                c++;
×
770
            else if (ch->colour() == QLCChannel::Magenta)
×
771
                m++;
×
772
            else if (ch->colour() == QLCChannel::Yellow)
×
773
                y++;
×
774
            else
775
                nocol++;
×
776
        }
777
        else if (ch->group() == QLCChannel::Shutter)
×
778
        {
779
            if (ch->searchCapability(/*S/s*/"trobe", false) != NULL)
×
780
                strobe++;
×
781
        }
782
        else if (ch->group() == QLCChannel::Gobo)
×
783
        {
784
            gobo++;
×
785
        }
786
        else if (ch->group() == QLCChannel::Colour)
×
787
        {
788
            colour++;
×
789
        }
790
        else if (ch->name().contains("strobe", Qt::CaseInsensitive) == true)
×
791
        {
792
            strobe++;
×
793
        }
794
        else if (ch->name().contains("haze", Qt::CaseInsensitive) == true)
×
795
        {
796
            haze++;
×
797
        }
798
        else if (ch->name().contains("smoke", Qt::CaseInsensitive) == true)
×
799
        {
800
            smoke++;
×
801
        }
802
    }
803

804
    if (pan >= 2 && tilt >= 2)
×
805
        return QLCFixtureDef::MovingHead; // Quite probable, few scanners with 16bit addressing
806
    else if (pan == 1 && tilt == 1)
×
807
        return QLCFixtureDef::Scanner; // Quite probable, though some moving heads are only 8bit
808
    else if (gobo > 0)
×
809
        return QLCFixtureDef::Flower; // No pan/tilt, but gobo, fairly certain
810
    else if (colour > 0 || (r > 0 && g > 0 && b > 0) || (c > 0 && m > 0 && y > 0))
×
811
        return QLCFixtureDef::ColorChanger; // No pan/tilt/gobos, but RGB/CMY mixing or dichro
812
    else if (strobe > 0)
×
813
        return QLCFixtureDef::Strobe; // Duh.
814
    else if (smoke > 0)
×
815
        return QLCFixtureDef::Smoke; // Duh.
816
    else if (haze > 0)
×
817
        return QLCFixtureDef::Hazer; // Duh.
818
    else if (nocol > 0)
×
819
        return QLCFixtureDef::Dimmer; // Kinda..mmmmh..
820
    else
821
        return QLCFixtureDef::Other; // Give up
×
822
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc