Skip to content

Commit 48bab26

Browse files
authored
Plugin and fixture test improvements (#913)
* Refactor Millumin plugin: Build new fixture JSON * QLC+: Create either global *or* mode physical * QLC+: Get pan/tiltMax also from fixture channels * QLC+: Fix category order * Fix wrong category order in fixture * Fixture test: Add mutually exclusive categories
1 parent 56c8bfc commit 48bab26

4 files changed

Lines changed: 106 additions & 78 deletions

File tree

fixtures/eurolite/led-pix-144.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json",
33
"name": "LED PIX-144",
4-
"categories": ["Color Changer", "Pixel Bar"],
4+
"categories": ["Pixel Bar", "Color Changer"],
55
"meta": {
66
"authors": ["Paolo Zanchi"],
77
"createDate": "2019-02-06",

plugins/millumin/export.js

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,58 +16,48 @@ module.exports.supportedOflVersion = `7.3.0`;
1616
module.exports.export = function exportMillumin(fixtures, options) {
1717
// one JSON file for each fixture
1818
const outFiles = fixtures.map(fixture => {
19-
let jsonData = JSON.parse(JSON.stringify(fixture.jsonObject));
20-
jsonData.$schema = `https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/schema-${module.exports.supportedOflVersion}/schemas/fixture.json`;
21-
22-
jsonData.fixtureKey = fixture.key;
23-
jsonData.manufacturerKey = fixture.manufacturer.key;
24-
jsonData.oflURL = `https://open-fixture-library.org/${fixture.manufacturer.key}/${fixture.key}`;
25-
26-
jsonData.categories = getDowngradedCategories(jsonData.categories);
27-
28-
if (jsonData.links) {
29-
if (jsonData.links.manual) {
30-
// replace links with manual URL in keys array
31-
const jsonKeys = Object.keys(jsonData);
32-
jsonKeys[jsonKeys.indexOf(`links`)] = `manualURL`;
33-
jsonData.manualURL = fixture.getLinksOfType(`manual`)[0];
34-
35-
// reorder JSON properties in jsonKeys order
36-
const reorderedJsonData = {};
37-
jsonKeys.forEach(key => {
38-
reorderedJsonData[key] = jsonData[key];
39-
});
40-
jsonData = reorderedJsonData;
41-
}
42-
else {
43-
delete jsonData.links;
44-
}
19+
const oflJson = JSON.parse(JSON.stringify(fixture.jsonObject));
20+
const milluminJson = {};
21+
22+
milluminJson.$schema = `https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/schema-${module.exports.supportedOflVersion}/schemas/fixture.json`;
23+
milluminJson.name = oflJson.name;
24+
addIfValidData(milluminJson, `shortName`, oflJson.shortName);
25+
milluminJson.categories = getDowngradedCategories(oflJson.categories);
26+
milluminJson.meta = oflJson.meta;
27+
addIfValidData(milluminJson, `comment`, oflJson.comment);
28+
29+
if (oflJson.links && oflJson.links.manual) {
30+
milluminJson.manualURL = fixture.getLinksOfType(`manual`)[0];
4531
}
4632

47-
delete jsonData.wheels;
33+
addIfValidData(milluminJson, `helpWanted`, oflJson.helpWanted);
34+
addIfValidData(milluminJson, `rdm`, oflJson.rdm);
35+
addIfValidData(milluminJson, `physical`, oflJson.physical);
36+
addIfValidData(milluminJson, `matrix`, getDowngradedMatrix(oflJson.matrix, fixture));
4837

49-
// resolve all pixel key constraints
50-
if (jsonData.matrix && jsonData.matrix.pixelGroups) {
51-
Object.keys(jsonData.matrix.pixelGroups).forEach(groupKey => {
52-
jsonData.matrix.pixelGroups[groupKey] = fixture.matrix.pixelGroups[groupKey];
38+
if (oflJson.availableChannels) {
39+
milluminJson.availableChannels = {};
40+
Object.entries(oflJson.availableChannels).forEach(([chKey, jsonChannel]) => {
41+
milluminJson.availableChannels[chKey] = getDowngradedChannel(chKey, jsonChannel, fixture);
5342
});
5443
}
5544

56-
if (jsonData.availableChannels) {
57-
Object.keys(jsonData.availableChannels).forEach(
58-
chKey => downgradeChannel(jsonData.availableChannels, chKey, fixture)
59-
);
45+
if (oflJson.templateChannels) {
46+
milluminJson.templateChannels = {};
47+
Object.entries(oflJson.templateChannels).forEach(([chKey, jsonChannel]) => {
48+
milluminJson.templateChannels[chKey] = getDowngradedChannel(chKey, jsonChannel, fixture);
49+
});
6050
}
6151

62-
if (jsonData.templateChannels) {
63-
Object.keys(jsonData.templateChannels).forEach(
64-
chKey => downgradeChannel(jsonData.templateChannels, chKey, fixture)
65-
);
66-
}
52+
milluminJson.modes = oflJson.modes;
53+
54+
milluminJson.fixtureKey = fixture.key;
55+
milluminJson.manufacturerKey = fixture.manufacturer.key;
56+
milluminJson.oflURL = `https://open-fixture-library.org/${fixture.manufacturer.key}/${fixture.key}`;
6757

6858
return {
6959
name: `${fixture.manufacturer.key}/${fixture.key}.json`,
70-
content: fixtureJsonStringify(jsonData),
60+
content: fixtureJsonStringify(milluminJson),
7161
mimetype: `application/ofl-fixture`,
7262
fixtures: [fixture]
7363
};
@@ -78,7 +68,7 @@ module.exports.export = function exportMillumin(fixtures, options) {
7868

7969
/**
8070
* Replaces the fixture's categories array with one that only includes categories
81-
* from OFL schema version 7.3.0.
71+
* from the supported OFL schema version.
8272
* @param {array.<string>} categories The fixture's categories array.
8373
* @returns {array.<string>} A filtered categories array.
8474
*/
@@ -97,13 +87,27 @@ function getDowngradedCategories(categories) {
9787
}
9888

9989
/**
100-
* Replaces the specified channel in the specified channels object with a downgraded version for schema 7.1.0.
101-
* @param {object} channelObject Either availableChannels or templateChannels.
90+
* @param {object|undefined} jsonMatrix The matrix JSON data (if present) that should be downgraded.
91+
* @param {Fixture} fixture The fixture the matrix belongs to.
92+
* @returns {object} A downgraded version of the specified matrix object.
93+
*/
94+
function getDowngradedMatrix(jsonMatrix, fixture) {
95+
if (jsonMatrix && jsonMatrix.pixelGroups) {
96+
Object.keys(jsonMatrix.pixelGroups).forEach(groupKey => {
97+
jsonMatrix.pixelGroups[groupKey] = fixture.matrix.pixelGroups[groupKey];
98+
});
99+
}
100+
101+
return jsonMatrix;
102+
}
103+
104+
/**
102105
* @param {string} channelKey A key that exists in given channelObject and specifies the channel that should be downgraded.
106+
* @param {object} jsonChannel The channel JSON data that should be downgraded.
103107
* @param {Fixture} fixture The fixture the channel belongs to.
108+
* @returns {object} A downgraded version of the specified channel object.
104109
*/
105-
function downgradeChannel(channelObject, channelKey, fixture) {
106-
const jsonChannel = channelObject[channelKey];
110+
function getDowngradedChannel(channelKey, jsonChannel, fixture) {
107111
const channel = new CoarseChannel(channelKey, jsonChannel, fixture);
108112

109113
const downgradedChannel = {};
@@ -119,8 +123,6 @@ function downgradeChannel(channelObject, channelKey, fixture) {
119123
addIfValidData(downgradedChannel, `crossfade`, channel.canCrossfade);
120124
addIfValidData(downgradedChannel, `precedence`, jsonChannel.precedence);
121125

122-
channelObject[channelKey] = downgradedChannel;
123-
124126
if (capabilitiesNeeded()) {
125127
downgradedChannel.capabilities = [];
126128

@@ -145,6 +147,8 @@ function downgradeChannel(channelObject, channelKey, fixture) {
145147
});
146148
}
147149

150+
return downgradedChannel;
151+
148152
/**
149153
* @returns {boolean} Whether or not it is needed to include capabilities in a downgraded version of this channel
150154
*/

plugins/qlcplus_4.12.1/export.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ module.exports.export = function exportQlcPlus(fixtures, options) {
6565
addChannel(xml, channel, fixture);
6666
}
6767

68+
const useGlobalPhysical = fixture.modes.every(mode => mode.physicalOverride === null);
69+
6870
for (const mode of fixture.modes) {
69-
addMode(xml, mode);
71+
addMode(xml, mode, !useGlobalPhysical);
7072
}
7173

72-
const allModesHavePhysical = fixture.modes.every(mode => mode.physicalOverride !== null);
73-
if (fixture.physical !== null && !allModesHavePhysical) {
74-
addPhysical(xml, fixture.physical, fixture);
74+
if (useGlobalPhysical) {
75+
addPhysical(xml, fixture.physical || new Physical({}), fixture);
7576
}
7677

7778
xml.dtd(``);
@@ -604,8 +605,9 @@ function getCapabilityPreset(capability) {
604605
/**
605606
* @param {object} xml The xmlbuilder <FixtureDefinition> object.
606607
* @param {Mode} mode The OFL mode object.
608+
* @param {boolean} createPhysical Whether to add a Physical XML element to the mode.
607609
*/
608-
function addMode(xml, mode) {
610+
function addMode(xml, mode, createPhysical) {
609611
const xmlMode = xml.element({
610612
Mode: {
611613
'@Name': mode.name
@@ -617,8 +619,8 @@ function addMode(xml, mode) {
617619
mode.physical.focusTiltMax === Number.POSITIVE_INFINITY
618620
);
619621

620-
if (mode.physicalOverride !== null || hasPanTiltInfinite) {
621-
addPhysical(xmlMode, mode.physical, mode.fixture, hasPanTiltInfinite ? mode : null);
622+
if (createPhysical) {
623+
addPhysical(xmlMode, mode.physical || new Physical({}), mode.fixture, hasPanTiltInfinite ? mode : null);
622624
}
623625

624626
mode.channels.forEach((channel, index) => {
@@ -686,7 +688,8 @@ function addPhysical(xmlParentNode, physical, fixture, mode) {
686688

687689
if (panTiltMax === Number.POSITIVE_INFINITY) {
688690
try {
689-
const panTiltChannel = mode.channels.find(
691+
const channels = mode ? mode.channels : fixture.coarseChannels;
692+
const panTiltChannel = channels.find(
690693
ch => `capabilities` in ch && ch.capabilities.length === 1 &&
691694
ch.capabilities[0].type === panOrTilt && ch.capabilities[0].angle[0].unit === `deg`
692695
);
@@ -798,13 +801,18 @@ function addHeads(xmlMode, mode) {
798801
* @returns {string} The first of the fixture's categories that is supported by QLC+, defaults to 'Other'.
799802
*/
800803
function getFixtureType(fixture) {
801-
// see https://github.com/OpenLightingProject/open-fixture-library/issues/581
802-
if (fixture.categories.includes(`Pixel Bar`)) {
803-
return isBeamBar() ? `LED Bar (Beams)` : `LED Bar (Pixels)`;
804-
}
804+
const replaceCats = {
805+
// see https://github.com/OpenLightingProject/open-fixture-library/issues/581
806+
'Pixel Bar': isBeamBar() ? `LED Bar (Beams)` : `LED Bar (Pixels)`
807+
};
805808

806809
const ignoredCats = [`Blinder`, `Matrix`, `Stand`];
807-
return fixture.categories.find(cat => !ignoredCats.includes(cat)) || `Other`;
810+
811+
return fixture.categories.map(
812+
cat => (cat in replaceCats ? replaceCats[cat] : cat)
813+
).find(
814+
cat => !ignoredCats.includes(cat)
815+
) || `Other`;
808816

809817

810818
/**

tests/fixture-valid.js

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,12 @@ function checkFixture(manKey, fixKey, fixtureJson, uniqueValues = null) {
10381038
* Checks if the used channels fits to the fixture's categories and raise warnings suggesting to add/remove a category.
10391039
*/
10401040
function checkCategories() {
1041+
const mutuallyExclusiveGroups = [
1042+
[`Moving Head`, `Scanner`],
1043+
[`Pixel Bar`, `Flower`],
1044+
[`Pixel Bar`, `Stand`]
1045+
];
1046+
10411047
const categories = {
10421048
'Color Changer': {
10431049
isSuggested: isColorChanger(),
@@ -1078,21 +1084,36 @@ function checkFixture(manKey, fixKey, fixtureJson, uniqueValues = null) {
10781084
}
10791085
};
10801086

1081-
for (const categoryName of Object.keys(categories)) {
1082-
const categoryUsed = fixture.categories.includes(categoryName);
1083-
const category = categories[categoryName];
1087+
Object.entries(categories).forEach(([categoryName, category]) => {
1088+
const isCategoryUsed = fixture.categories.includes(categoryName);
10841089

1085-
if (!(`isInvalid` in category)) {
1086-
category.isInvalid = !category.isSuggested;
1087-
}
1090+
if (!isCategoryUsed && category.isSuggested) {
1091+
// don't suggest this category if another mutually exclusive category is used
1092+
const exclusiveGroups = mutuallyExclusiveGroups.filter(
1093+
group => group.includes(categoryName)
1094+
);
1095+
const isForbiddenByGroup = exclusiveGroups.some(
1096+
group => group.some(
1097+
cat => fixture.categories.includes(cat)
1098+
)
1099+
);
10881100

1089-
if (!categoryUsed && category.isSuggested) {
1090-
result.warnings.push(`Category '${categoryName}' suggested since ${category.suggestedPhrase}.`);
1101+
if (!isForbiddenByGroup) {
1102+
result.warnings.push(`Category '${categoryName}' suggested since ${category.suggestedPhrase}.`);
1103+
}
10911104
}
1092-
else if (categoryUsed && category.isInvalid) {
1105+
else if (isCategoryUsed && category.isInvalid) {
10931106
result.errors.push(`Category '${categoryName}' invalid since ${category.invalidPhrase}.`);
10941107
}
1095-
}
1108+
});
1109+
1110+
mutuallyExclusiveGroups.forEach(group => {
1111+
const usedCategories = group.filter(cat => fixture.categories.includes(cat));
1112+
1113+
if (usedCategories.length >= 2) {
1114+
result.errors.push(`Categories '${usedCategories.join(`', '`)}' can't be used together.`);
1115+
}
1116+
});
10961117

10971118
/**
10981119
* @returns {boolean} Whether the 'Color Changer' category is suggested.
@@ -1184,12 +1205,7 @@ function checkFixture(manKey, fixKey, fixtureJson, uniqueValues = null) {
11841205
* @returns {boolean} True if a matrix with only one axis, which has more than 4 pixels, is defined.
11851206
*/
11861207
function isPixelBar() {
1187-
const mutualExclusiveCategories = [`Flower`, `Stand`];
1188-
const hasMutualExclusiveCategory = fixture.categories.some(
1189-
cat => mutualExclusiveCategories.includes(cat)
1190-
);
1191-
1192-
if (hasMutualExclusiveCategory || fixture.matrix === null) {
1208+
if (fixture.matrix === null) {
11931209
return false;
11941210
}
11951211

0 commit comments

Comments
 (0)