@rocketshow/designer 1.44.0 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,7 +28,7 @@ import * as i11 from 'ngx-bootstrap/popover';
28
28
  import { PopoverModule } from 'ngx-bootstrap/popover';
29
29
  import * as i10 from 'ngx-bootstrap/typeahead';
30
30
  import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
31
- import * as i4$1 from 'ngx-bootstrap-slider';
31
+ import * as i3$3 from 'ngx-bootstrap-slider';
32
32
  import { NgxBootstrapSliderModule } from 'ngx-bootstrap-slider';
33
33
  import * as i9 from '@angular/common';
34
34
  import * as i7 from 'ngx-sortablejs-plus';
@@ -1054,13 +1054,107 @@ class UuidService {
1054
1054
  }]
1055
1055
  }], () => [], null); })();
1056
1056
 
1057
+ class CachedFixturePixel {
1058
+ }
1059
+
1060
+ class FixturePixelGroupResolveService {
1061
+ matchNumberRule(value, ruleRaw) {
1062
+ const rule = ruleRaw.trim().toLowerCase();
1063
+ // comparisons: <=5, >=5, =5
1064
+ const m = rule.match(/^(<=|>=|=)\s*(-?\d+)$/);
1065
+ if (m) {
1066
+ const op = m[1];
1067
+ const n = Number(m[2]);
1068
+ if (op === '<=')
1069
+ return value <= n;
1070
+ if (op === '>=')
1071
+ return value >= n;
1072
+ return value === n;
1073
+ }
1074
+ // even / odd
1075
+ if (rule === 'even')
1076
+ return value % 2 === 0;
1077
+ if (rule === 'odd')
1078
+ return Math.abs(value % 2) === 1;
1079
+ // 3n, 3n+1, 3n+2, etc.
1080
+ const mn = rule.match(/^(\d+)n(?:\+(\d+))?$/);
1081
+ if (mn) {
1082
+ const base = Number(mn[1]); // e.g. 3
1083
+ const rem = Number(mn[2] ?? '0'); // e.g. 1
1084
+ // handle negative values reasonably too
1085
+ const mod = ((value % base) + base) % base;
1086
+ return mod === ((rem % base) + base) % base;
1087
+ }
1088
+ // If you want, you can treat a plain number as "=number"
1089
+ const asNum = Number(rule);
1090
+ if (!Number.isNaN(asNum))
1091
+ return value === asNum;
1092
+ throw new Error(`Unknown numeric rule: "${ruleRaw}"`);
1093
+ }
1094
+ matchesAllConstraints(p, def) {
1095
+ // AND across present fields
1096
+ if (def.x && !def.x.every((rule) => this.matchNumberRule(p.x, rule)))
1097
+ return false;
1098
+ if (def.y && !def.y.every((rule) => this.matchNumberRule(p.y, rule)))
1099
+ return false;
1100
+ if (def.z && !def.z.every((rule) => this.matchNumberRule(p.z, rule)))
1101
+ return false;
1102
+ if (def.name) {
1103
+ // Interpret each entry as a regex; AND across them (change to "some" if you want OR)
1104
+ const ok = def.name.every((pattern) => {
1105
+ const re = new RegExp(pattern);
1106
+ return re.test(p.key);
1107
+ });
1108
+ if (!ok)
1109
+ return false;
1110
+ }
1111
+ return true;
1112
+ }
1113
+ resolveGroupPixels(group, pixelGroups, allPixels) {
1114
+ const def = pixelGroups[group];
1115
+ if (!def)
1116
+ return []; // unknown group
1117
+ // 1) "all"
1118
+ if (def === 'all')
1119
+ return allPixels.slice();
1120
+ // 2) explicit keys
1121
+ if (Array.isArray(def)) {
1122
+ const wanted = new Set(def);
1123
+ return allPixels.filter((p) => wanted.has(p.key));
1124
+ }
1125
+ // 3) constraints object
1126
+ return allPixels.filter((p) => this.matchesAllConstraints(p, def));
1127
+ }
1128
+ getPixelsForKeyOrGroup(profile, allPixelKeys, keyOrGroup) {
1129
+ let result = [];
1130
+ // search for a key first
1131
+ const pixel = allPixelKeys.find((p) => p.key === keyOrGroup);
1132
+ if (pixel)
1133
+ return [pixel];
1134
+ // search for a group
1135
+ if (profile.matrix.pixelGroups[keyOrGroup]) {
1136
+ result.push(...this.resolveGroupPixels(keyOrGroup, profile.matrix.pixelGroups, allPixelKeys));
1137
+ }
1138
+ return result;
1139
+ }
1140
+ static { this.ɵfac = function FixturePixelGroupResolveService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FixturePixelGroupResolveService)(); }; }
1141
+ static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FixturePixelGroupResolveService, factory: FixturePixelGroupResolveService.ɵfac, providedIn: 'root' }); }
1142
+ }
1143
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FixturePixelGroupResolveService, [{
1144
+ type: Injectable,
1145
+ args: [{
1146
+ providedIn: 'root',
1147
+ }]
1148
+ }], null, null); })();
1149
+
1057
1150
  class FixtureService {
1058
- constructor(http, projectService, translateService, toastrService, uuidService) {
1151
+ constructor(http, projectService, translateService, toastrService, uuidService, fixturePixelGroupResolveService) {
1059
1152
  this.http = http;
1060
1153
  this.projectService = projectService;
1061
1154
  this.translateService = translateService;
1062
1155
  this.toastrService = toastrService;
1063
1156
  this.uuidService = uuidService;
1157
+ this.fixturePixelGroupResolveService = fixturePixelGroupResolveService;
1064
1158
  this.cachedFixtures = [];
1065
1159
  // is the side-tab settings currently selected?
1066
1160
  this.settingsSelection = false;
@@ -1078,7 +1172,7 @@ class FixtureService {
1078
1172
  }
1079
1173
  getCachedFixtureByUuid(uuid, pixelKey) {
1080
1174
  for (const fixture of this.cachedFixtures) {
1081
- if (this.fixtureUuidAndPixelKeyEquals(fixture.fixture.uuid, uuid, fixture.pixelKey, pixelKey)) {
1175
+ if (this.fixtureUuidAndPixelKeyEquals(fixture.fixture.uuid, uuid, fixture.pixel?.key, pixelKey)) {
1082
1176
  return fixture;
1083
1177
  }
1084
1178
  }
@@ -1087,7 +1181,7 @@ class FixtureService {
1087
1181
  return undefined;
1088
1182
  }
1089
1183
  for (const fixture of this.cachedFixtures) {
1090
- if (!fixture.pixelKey && fixture.fixture.uuid === uuid) {
1184
+ if (!fixture.pixel && fixture.fixture.uuid === uuid) {
1091
1185
  return fixture;
1092
1186
  }
1093
1187
  }
@@ -1324,40 +1418,92 @@ class FixtureService {
1324
1418
  channels.push(cachedFixtureChannel);
1325
1419
  }
1326
1420
  alphanumericSort(a, b) {
1327
- return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });
1421
+ return a.key.localeCompare(b.key, undefined, { numeric: true, sensitivity: 'base' });
1422
+ }
1423
+ evenlySpacedPosCm(index, count, size) {
1424
+ if (count <= 0)
1425
+ return 0;
1426
+ if (index < 0 || index >= count)
1427
+ return 0;
1428
+ if (count === 1)
1429
+ return size / 2;
1430
+ const step = size / (count + 1); // equal edge margins
1431
+ const pos = (index + 1) * step; // center position
1432
+ return pos;
1433
+ }
1434
+ createPixel(key, x, y, z, profile) {
1435
+ let pixel = new CachedFixturePixel();
1436
+ if (key) {
1437
+ pixel.key = key;
1438
+ }
1439
+ else {
1440
+ // no key available. generate a unique one based on the dimensions
1441
+ pixel.key = 'Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1);
1442
+ }
1443
+ pixel.x = x;
1444
+ pixel.y = y;
1445
+ pixel.z = z;
1446
+ // calculate the position of the beam, based on the physical dimensions
1447
+ // dimensions in mm
1448
+ const widthCm = profile.physical.dimensions[0] / 10;
1449
+ const heightCm = profile.physical.dimensions[1] / 10;
1450
+ const depthCm = profile.physical.dimensions[2] / 10;
1451
+ let maxX = 0;
1452
+ let maxY = 0;
1453
+ let maxZ = 0;
1454
+ if (profile.matrix.pixelCount) {
1455
+ maxX = profile.matrix.pixelCount[0];
1456
+ maxY = profile.matrix.pixelCount[1];
1457
+ maxZ = profile.matrix.pixelCount[2];
1458
+ }
1459
+ else {
1460
+ maxX = profile.matrix.pixelKeys.length;
1461
+ maxY = profile.matrix.pixelKeys[0].length;
1462
+ maxZ = profile.matrix.pixelKeys[0][0].length;
1463
+ }
1464
+ // distribute the pixels with even spacing
1465
+ pixel.positionX = this.evenlySpacedPosCm(x, maxX, widthCm) - widthCm / 2;
1466
+ pixel.positionY = this.evenlySpacedPosCm(y, maxY, heightCm) - heightCm / 2;
1467
+ pixel.positionZ = this.evenlySpacedPosCm(z, maxZ, depthCm) - depthCm / 2;
1468
+ return pixel;
1328
1469
  }
1329
1470
  getAllPixelKeys(profile) {
1330
1471
  let result = [];
1331
- if (!profile.matrix.pixelKeys) {
1472
+ if (!profile.matrix || !profile.matrix.pixelKeys) {
1332
1473
  return result;
1333
1474
  }
1334
- for (let pixelKeyX of profile.matrix.pixelKeys) {
1335
- for (let pixelKeyY of pixelKeyX) {
1336
- for (let pixelKeyZ of pixelKeyY) {
1337
- if (pixelKeyZ) {
1338
- result.push(pixelKeyZ);
1475
+ for (let x = 0; x < profile.matrix.pixelKeys.length; x++) {
1476
+ for (let y = 0; y < profile.matrix.pixelKeys[x].length; y++) {
1477
+ for (let z = 0; z < profile.matrix.pixelKeys[x][y].length; z++) {
1478
+ const pixelKey = profile.matrix.pixelKeys[x][y][z];
1479
+ if (pixelKey) {
1480
+ result.push(this.createPixel(pixelKey, x, y, z, profile));
1339
1481
  }
1340
1482
  }
1341
1483
  }
1342
1484
  }
1343
1485
  return result;
1344
1486
  }
1345
- getAllPixelGroups(profile) {
1487
+ getAllPixelsInGroups(profile, allPixelKeys) {
1346
1488
  let result = [];
1347
- if (!profile.matrix.pixelGroups) {
1489
+ if (!profile.matrix || !profile.matrix.pixelGroups) {
1348
1490
  return result;
1349
1491
  }
1350
1492
  for (const property of Object.keys(profile.matrix.pixelGroups)) {
1351
- result.push(property);
1493
+ result.concat(this.fixturePixelGroupResolveService.resolveGroupPixels(property, profile.matrix.pixelGroups, allPixelKeys));
1352
1494
  }
1353
1495
  return result;
1354
1496
  }
1355
- getPixelKeysInOrder(profile, repeatFor) {
1497
+ // get pixels in order for a specific channel repeatFor
1498
+ getPixelsInOrder(profile, repeatFor) {
1499
+ let allPixelKeys = this.getAllPixelKeys(profile);
1356
1500
  let result = [];
1357
- // could contain just a single string (e.g. 'eachPixelABC') or
1501
+ // could contain different things
1358
1502
  if (repeatFor.length > 1) {
1359
- // an array of pixel (groups)
1360
- result = repeatFor;
1503
+ // an array of pixel keys or groups
1504
+ for (let entry of repeatFor) {
1505
+ result = result.concat(this.fixturePixelGroupResolveService.getPixelsForKeyOrGroup(profile, allPixelKeys, entry));
1506
+ }
1361
1507
  }
1362
1508
  else if (repeatFor.length === 0) {
1363
1509
  return result;
@@ -1365,22 +1511,23 @@ class FixtureService {
1365
1511
  else if (repeatFor[0] === 'eachPixelABC') {
1366
1512
  // Gets computed into an alphanumerically sorted list of all pixelKeys
1367
1513
  if (profile.matrix.pixelKeys) {
1368
- result = result.concat(this.getAllPixelKeys(profile));
1514
+ result = result.concat(allPixelKeys);
1515
+ result.sort(this.alphanumericSort);
1516
+ result.sort((a, b) => a.key.localeCompare(b.key, undefined, { numeric: true, sensitivity: 'base' }));
1369
1517
  }
1370
1518
  else if (profile.matrix.pixelCount.length === 3) {
1371
1519
  for (let x = 0; x < profile.matrix.pixelCount[0]; x++) {
1372
1520
  for (let y = 0; y < profile.matrix.pixelCount[1]; y++) {
1373
1521
  for (let z = 0; z < profile.matrix.pixelCount[2]; z++) {
1374
- result.push('Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1));
1522
+ result.push(this.createPixel(null, x, y, z, profile));
1375
1523
  }
1376
1524
  }
1377
1525
  }
1378
1526
  }
1379
- result.sort(this.alphanumericSort);
1380
1527
  }
1381
1528
  else if (repeatFor[0] === 'eachPixelGroup') {
1382
1529
  // Gets computed into an array of all pixel group keys, ordered by appearance in the JSON file
1383
- result = result.concat(this.getAllPixelGroups(profile));
1530
+ result = result.concat(this.getAllPixelsInGroups(profile, allPixelKeys));
1384
1531
  }
1385
1532
  else if (repeatFor[0] === 'eachPixelXYZ') {
1386
1533
  if (profile.matrix.pixelKeys) {
@@ -1389,7 +1536,7 @@ class FixtureService {
1389
1536
  for (let z = 0; z < profile.matrix.pixelKeys[x][y].length; z++) {
1390
1537
  const pixel = profile.matrix.pixelKeys[x][y][z];
1391
1538
  if (pixel) {
1392
- result.push(pixel);
1539
+ result.push(this.createPixel(pixel, x, y, z, profile));
1393
1540
  }
1394
1541
  }
1395
1542
  }
@@ -1399,7 +1546,7 @@ class FixtureService {
1399
1546
  for (let x = 0; x < profile.matrix.pixelCount[0]; x++) {
1400
1547
  for (let y = 0; y < profile.matrix.pixelCount[1]; y++) {
1401
1548
  for (let z = 0; z < profile.matrix.pixelCount[2]; z++) {
1402
- result.push('Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1));
1549
+ result.push(this.createPixel(null, x, y, z, profile));
1403
1550
  }
1404
1551
  }
1405
1552
  }
@@ -1412,7 +1559,7 @@ class FixtureService {
1412
1559
  for (let y = 0; y < profile.matrix.pixelKeys[x].length; y++) {
1413
1560
  const pixel = profile.matrix.pixelKeys[x][y][z];
1414
1561
  if (pixel) {
1415
- result.push(pixel);
1562
+ result.push(this.createPixel(pixel, x, y, z, profile));
1416
1563
  }
1417
1564
  }
1418
1565
  }
@@ -1422,7 +1569,7 @@ class FixtureService {
1422
1569
  for (let x = 0; x < profile.matrix.pixelCount[0]; x++) {
1423
1570
  for (let z = 0; z < profile.matrix.pixelCount[2]; z++) {
1424
1571
  for (let y = 0; y < profile.matrix.pixelCount[1]; y++) {
1425
- result.push('Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1));
1572
+ result.push(this.createPixel(null, x, y, z, profile));
1426
1573
  }
1427
1574
  }
1428
1575
  }
@@ -1435,7 +1582,7 @@ class FixtureService {
1435
1582
  for (let z = 0; z < profile.matrix.pixelKeys[x][y].length; z++) {
1436
1583
  const pixel = profile.matrix.pixelKeys[x][y][z];
1437
1584
  if (pixel) {
1438
- result.push(pixel);
1585
+ result.push(this.createPixel(pixel, x, y, z, profile));
1439
1586
  }
1440
1587
  }
1441
1588
  }
@@ -1445,7 +1592,7 @@ class FixtureService {
1445
1592
  for (let y = 0; y < profile.matrix.pixelCount[1]; y++) {
1446
1593
  for (let x = 0; x < profile.matrix.pixelCount[0]; x++) {
1447
1594
  for (let z = 0; z < profile.matrix.pixelCount[2]; z++) {
1448
- result.push('Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1));
1595
+ result.push(this.createPixel(null, x, y, z, profile));
1449
1596
  }
1450
1597
  }
1451
1598
  }
@@ -1458,7 +1605,7 @@ class FixtureService {
1458
1605
  for (let x = 0; x < profile.matrix.pixelKeys.length; x++) {
1459
1606
  const pixel = profile.matrix.pixelKeys[x][y][z];
1460
1607
  if (pixel) {
1461
- result.push(pixel);
1608
+ result.push(this.createPixel(pixel, x, y, z, profile));
1462
1609
  }
1463
1610
  }
1464
1611
  }
@@ -1468,7 +1615,7 @@ class FixtureService {
1468
1615
  for (let y = 0; y < profile.matrix.pixelCount[1]; y++) {
1469
1616
  for (let z = 0; z < profile.matrix.pixelCount[2]; z++) {
1470
1617
  for (let x = 0; x < profile.matrix.pixelCount[0]; x++) {
1471
- result.push('Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1));
1618
+ result.push(this.createPixel(null, x, y, z, profile));
1472
1619
  }
1473
1620
  }
1474
1621
  }
@@ -1481,7 +1628,7 @@ class FixtureService {
1481
1628
  for (let y = 0; y < profile.matrix.pixelKeys[x].length; y++) {
1482
1629
  const pixel = profile.matrix.pixelKeys[x][y][z];
1483
1630
  if (pixel) {
1484
- result.push(pixel);
1631
+ result.push(this.createPixel(pixel, x, y, z, profile));
1485
1632
  }
1486
1633
  }
1487
1634
  }
@@ -1491,7 +1638,7 @@ class FixtureService {
1491
1638
  for (let z = 0; z < profile.matrix.pixelCount[2]; z++) {
1492
1639
  for (let x = 0; x < profile.matrix.pixelCount[0]; x++) {
1493
1640
  for (let y = 0; y < profile.matrix.pixelCount[1]; y++) {
1494
- result.push('Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1));
1641
+ result.push(this.createPixel(null, x, y, z, profile));
1495
1642
  }
1496
1643
  }
1497
1644
  }
@@ -1504,7 +1651,7 @@ class FixtureService {
1504
1651
  for (let x = 0; x < profile.matrix.pixelKeys.length; x++) {
1505
1652
  const pixel = profile.matrix.pixelKeys[x][y][z];
1506
1653
  if (pixel) {
1507
- result.push(pixel);
1654
+ result.push(this.createPixel(pixel, x, y, z, profile));
1508
1655
  }
1509
1656
  }
1510
1657
  }
@@ -1514,7 +1661,7 @@ class FixtureService {
1514
1661
  for (let z = 0; z < profile.matrix.pixelCount[2]; z++) {
1515
1662
  for (let y = 0; y < profile.matrix.pixelCount[1]; y++) {
1516
1663
  for (let x = 0; x < profile.matrix.pixelCount[0]; x++) {
1517
- result.push('Pixel ' + (x + 1) + '-' + (y + 1) + '-' + (z + 1));
1664
+ result.push(this.createPixel(null, x, y, z, profile));
1518
1665
  }
1519
1666
  }
1520
1667
  }
@@ -1564,6 +1711,8 @@ class FixtureService {
1564
1711
  }
1565
1712
  getCachedChannels(profile, mode, pixelKey) {
1566
1713
  const channels = [];
1714
+ const allPixelKeys = this.getAllPixelKeys(profile);
1715
+ const allPixelsInGroups = this.getAllPixelsInGroups(profile, allPixelKeys);
1567
1716
  if (!mode) {
1568
1717
  return channels;
1569
1718
  }
@@ -1575,18 +1724,19 @@ class FixtureService {
1575
1724
  for (const availableChannelName of Object.keys(profile.availableChannels)) {
1576
1725
  if (channel.name === availableChannelName) {
1577
1726
  const availableChannel = profile.availableChannels[availableChannelName];
1727
+ channelFound = true;
1578
1728
  this.addCachedChannel(channels, availableChannel, availableChannelName, null, profile);
1579
1729
  }
1580
1730
  }
1581
1731
  // check the template channels, if not found in the available channels
1582
1732
  if (!channelFound) {
1583
1733
  for (const templateChannelName of Object.keys(profile.templateChannels)) {
1584
- let existingPixelKeys = this.getAllPixelKeys(profile).concat(this.getAllPixelGroups(profile));
1734
+ let existingPixelKeys = allPixelKeys.concat(allPixelsInGroups);
1585
1735
  for (const existingPixelKey of existingPixelKeys) {
1586
- const channelName = this.getChannelNameWithPixelKey(templateChannelName, existingPixelKey);
1736
+ const channelName = this.getChannelNameWithPixelKey(templateChannelName, existingPixelKey.key);
1587
1737
  if (channel.name === channelName) {
1588
1738
  const templateChannel = profile.templateChannels[templateChannelName];
1589
- this.addCachedChannel(channels, templateChannel, channelName, existingPixelKey, profile);
1739
+ this.addCachedChannel(channels, templateChannel, channelName, existingPixelKey.key, profile);
1590
1740
  }
1591
1741
  }
1592
1742
  }
@@ -1594,17 +1744,17 @@ class FixtureService {
1594
1744
  }
1595
1745
  else {
1596
1746
  // reference a channel through a pixel matrix
1597
- const availablePixelKeys = this.getPixelKeysInOrder(profile, channel.repeatFor);
1747
+ const availablePixels = this.getPixelsInOrder(profile, channel.repeatFor);
1598
1748
  if (channel.channelOrder === 'perPixel') {
1599
1749
  // each channel for each pixel
1600
- for (const availablePixelKey of availablePixelKeys) {
1601
- if (availablePixelKey === pixelKey) {
1750
+ for (const availablePixel of availablePixels) {
1751
+ if (availablePixel.key === pixelKey) {
1602
1752
  for (const modeTemplateChannelName of channel.templateChannels) {
1603
1753
  for (const templateChannelName of Object.keys(profile.templateChannels)) {
1604
1754
  // don't check the fine channels. only add the coarse channel.
1605
1755
  if (modeTemplateChannelName === templateChannelName) {
1606
1756
  const templateChannel = profile.templateChannels[templateChannelName];
1607
- this.addCachedChannel(channels, templateChannel, templateChannelName, availablePixelKey, profile);
1757
+ this.addCachedChannel(channels, templateChannel, templateChannelName, availablePixel.key, profile);
1608
1758
  }
1609
1759
  }
1610
1760
  }
@@ -1614,13 +1764,13 @@ class FixtureService {
1614
1764
  else if (channel.channelOrder === 'perChannel') {
1615
1765
  // each pixel for each channel
1616
1766
  for (const modeTemplateChannelName of channel.templateChannels) {
1617
- for (const availablePixelKey of availablePixelKeys) {
1618
- if (availablePixelKey === pixelKey) {
1767
+ for (const availablePixel of availablePixels) {
1768
+ if (availablePixel.key === pixelKey) {
1619
1769
  for (const templateChannelName of Object.keys(profile.templateChannels)) {
1620
1770
  // don't check the fine channels. only add the coarse channel.
1621
1771
  if (modeTemplateChannelName === templateChannelName) {
1622
1772
  const templateChannel = profile.templateChannels[templateChannelName];
1623
- this.addCachedChannel(channels, templateChannel, templateChannelName, availablePixelKey, profile);
1773
+ this.addCachedChannel(channels, templateChannel, templateChannelName, availablePixel.key, profile);
1624
1774
  }
1625
1775
  }
1626
1776
  }
@@ -1631,17 +1781,20 @@ class FixtureService {
1631
1781
  }
1632
1782
  return channels;
1633
1783
  }
1634
- fixtureGetUniquePixelKeys(fixture) {
1635
- let pixelKeys = new Set();
1784
+ // get all used pixels for all channels in the currently used fixture mode
1785
+ fixtureGetUniquePixels(fixture) {
1786
+ let pixels = [];
1636
1787
  let profile = this.getProfileByUuid(fixture.profileUuid);
1637
1788
  let mode = this.getModeByFixture(profile, fixture);
1638
1789
  // add the unique pixel keys
1639
1790
  for (let channel of mode.channels) {
1640
- this.getPixelKeysInOrder(profile, channel.repeatFor).forEach((str) => {
1641
- pixelKeys.add(str);
1791
+ this.getPixelsInOrder(profile, channel.repeatFor).forEach((pixel) => {
1792
+ if (pixels.filter((p) => p.key === pixel.key).length === 0) {
1793
+ pixels.push(pixel);
1794
+ }
1642
1795
  });
1643
1796
  }
1644
- return Array.from(pixelKeys);
1797
+ return pixels;
1645
1798
  }
1646
1799
  // does the fixture have a general channel without reference to specific pixel keys in the current mode?
1647
1800
  fixtureHasGeneralChannel(fixture) {
@@ -1649,7 +1802,7 @@ class FixtureService {
1649
1802
  let mode = this.getModeByFixture(profile, fixture);
1650
1803
  // add the unique pixel keys
1651
1804
  for (let channel of mode.channels) {
1652
- if (this.getPixelKeysInOrder(profile, channel.repeatFor).length === 0) {
1805
+ if (this.getPixelsInOrder(profile, channel.repeatFor).length === 0) {
1653
1806
  return true;
1654
1807
  }
1655
1808
  }
@@ -1668,14 +1821,14 @@ class FixtureService {
1668
1821
  cachedFixture.channels = this.getCachedChannels(cachedFixture.profile, cachedFixture.mode, null);
1669
1822
  this.cachedFixtures.push(cachedFixture);
1670
1823
  }
1671
- const pixelKeys = this.fixtureGetUniquePixelKeys(fixture);
1672
- for (let pixelKey of pixelKeys) {
1824
+ const pixels = this.fixtureGetUniquePixels(fixture);
1825
+ for (let pixel of pixels) {
1673
1826
  const cachedFixture = new CachedFixture();
1674
1827
  cachedFixture.fixture = fixture;
1675
- cachedFixture.pixelKey = pixelKey;
1828
+ cachedFixture.pixel = pixel;
1676
1829
  cachedFixture.profile = this.getProfileByUuid(fixture.profileUuid);
1677
1830
  cachedFixture.mode = this.getModeByFixture(cachedFixture.profile, fixture);
1678
- cachedFixture.channels = this.getCachedChannels(cachedFixture.profile, cachedFixture.mode, pixelKey);
1831
+ cachedFixture.channels = this.getCachedChannels(cachedFixture.profile, cachedFixture.mode, pixel.key);
1679
1832
  this.cachedFixtures.push(cachedFixture);
1680
1833
  }
1681
1834
  }
@@ -1788,7 +1941,7 @@ class FixtureService {
1788
1941
  });
1789
1942
  }
1790
1943
  }
1791
- static { this.ɵfac = function FixtureService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FixtureService)(i0.ɵɵinject(i1.HttpClient), i0.ɵɵinject(ProjectService), i0.ɵɵinject(i4.TranslateService), i0.ɵɵinject(i3$1.ToastrService), i0.ɵɵinject(UuidService)); }; }
1944
+ static { this.ɵfac = function FixtureService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FixtureService)(i0.ɵɵinject(i1.HttpClient), i0.ɵɵinject(ProjectService), i0.ɵɵinject(i4.TranslateService), i0.ɵɵinject(i3$1.ToastrService), i0.ɵɵinject(UuidService), i0.ɵɵinject(FixturePixelGroupResolveService)); }; }
1792
1945
  static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FixtureService, factory: FixtureService.ɵfac, providedIn: 'root' }); }
1793
1946
  }
1794
1947
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FixtureService, [{
@@ -1796,7 +1949,7 @@ class FixtureService {
1796
1949
  args: [{
1797
1950
  providedIn: 'root',
1798
1951
  }]
1799
- }], () => [{ type: i1.HttpClient }, { type: ProjectService }, { type: i4.TranslateService }, { type: i3$1.ToastrService }, { type: UuidService }], null); })();
1952
+ }], () => [{ type: i1.HttpClient }, { type: ProjectService }, { type: i4.TranslateService }, { type: i3$1.ToastrService }, { type: UuidService }, { type: FixturePixelGroupResolveService }], null); })();
1800
1953
 
1801
1954
  class PreviewMeshService {
1802
1955
  constructor() {
@@ -2008,12 +2161,12 @@ class PresetService {
2008
2161
  this.selectedPreset.fixtures.push(presetFixture);
2009
2162
  }
2010
2163
  }
2011
- const pixelKeys = this.fixtureService.fixtureGetUniquePixelKeys(fixture);
2012
- for (const pixelKey of pixelKeys) {
2013
- if (!this.fixtureIsSelected(fixture, pixelKey)) {
2164
+ const pixels = this.fixtureService.fixtureGetUniquePixels(fixture);
2165
+ for (const pixel of pixels) {
2166
+ if (!this.fixtureIsSelected(fixture, pixel.key)) {
2014
2167
  const presetFixture = new PresetFixture();
2015
2168
  presetFixture.fixtureUuid = fixture.uuid;
2016
- presetFixture.pixelKey = pixelKey;
2169
+ presetFixture.pixelKey = pixel.key;
2017
2170
  this.selectedPreset.fixtures.push(presetFixture);
2018
2171
  }
2019
2172
  }
@@ -2276,7 +2429,7 @@ class PresetService {
2276
2429
  if (this.presetFixtueEquals(presetFixture, projectFixture)) {
2277
2430
  const fixture = this.fixtureService.getCachedFixtureByUuid(presetFixture.fixtureUuid, presetFixture.pixelKey);
2278
2431
  if (fixture.profile.uuid === profile.uuid) {
2279
- const exists = modeAndPixelKeys.some((item) => (item.mode === fixture.mode && !item.pixelKey && !fixture.pixelKey) || item.pixelKey === fixture.pixelKey);
2432
+ const exists = modeAndPixelKeys.some((item) => (item.mode === fixture.mode && !item.pixelKey && !fixture.pixel?.key) || item.pixelKey === fixture.pixel?.key);
2280
2433
  if (!exists) {
2281
2434
  modeAndPixelKeys.push({
2282
2435
  mode: fixture.mode,
@@ -3219,6 +3372,8 @@ class PreviewService {
3219
3372
  this.timelineService = timelineService;
3220
3373
  this.projectService = projectService;
3221
3374
  this.doUpdateFixtureSetup = new Subject();
3375
+ this.doUpdateStageAndPositions = new Subject();
3376
+ this.stageAndPositionsDirty = true;
3222
3377
  this.stageMeshes = [];
3223
3378
  this.stageMaterial = new THREE.MeshStandardMaterial({
3224
3379
  color: 0x0d0d0d,
@@ -3230,8 +3385,12 @@ class PreviewService {
3230
3385
  emissive: 0x0d0d0d,
3231
3386
  });
3232
3387
  this.fixtureSelectedMaterial = new THREE.MeshLambertMaterial({
3233
- color: 0xff00ff,
3234
- emissive: 0xff00ff,
3388
+ color: 0x660066,
3389
+ emissive: 0xaa00aa,
3390
+ emissiveIntensity: 0.000000000000000000001,
3391
+ });
3392
+ this.updateStageAndPositionsSubscription = this.doUpdateStageAndPositions.subscribe(() => {
3393
+ this.stageAndPositionsDirty = true;
3235
3394
  });
3236
3395
  }
3237
3396
  getAlreadyCalculatedFixture(fixtures, fixtureIndex) {
@@ -3240,7 +3399,9 @@ class PreviewService {
3240
3399
  for (let i = 0; i < fixtureIndex; i++) {
3241
3400
  const calculatedFixture = fixtures[i];
3242
3401
  if (calculatedFixture.fixture.dmxUniverseUuid === fixtures[fixtureIndex].fixture.dmxUniverseUuid &&
3243
- calculatedFixture.fixture.dmxFirstChannel === fixtures[fixtureIndex].fixture.dmxFirstChannel) {
3402
+ calculatedFixture.fixture.dmxFirstChannel === fixtures[fixtureIndex].fixture.dmxFirstChannel &&
3403
+ ((!calculatedFixture.pixel?.key && !fixtures[fixtureIndex].pixel?.key) ||
3404
+ calculatedFixture.pixel?.key === fixtures[fixtureIndex].pixel?.key)) {
3244
3405
  return calculatedFixture;
3245
3406
  }
3246
3407
  }
@@ -3564,7 +3725,7 @@ class PreviewService {
3564
3725
  }
3565
3726
  for (const preset of presets) {
3566
3727
  // search for this fixture in the preset and get it's preset-specific index (for chasing effects)
3567
- const fixtureIndex = this.getFixtureIndex(preset.preset, cachedFixture.fixture.uuid, cachedFixture.pixelKey);
3728
+ const fixtureIndex = this.getFixtureIndex(preset.preset, cachedFixture.fixture.uuid, cachedFixture.pixel?.key);
3568
3729
  if (fixtureIndex >= 0) {
3569
3730
  // this fixture is also in the preset -> mix the required values (overwrite existing values,
3570
3731
  // if set multiple times)
@@ -3675,7 +3836,7 @@ class PreviewService {
3675
3836
  }], () => [{ type: PresetService }, { type: FixtureService }, { type: SceneService }, { type: TimelineService }, { type: ProjectService }], null); })();
3676
3837
 
3677
3838
  class Fixture3d {
3678
- constructor(fixtureService, previewService, fixture, scene, hasSpotLight = false) {
3839
+ constructor(fixtureService, previewService, fixture, scene, fixtureGroup, hasSpotLight = false, hasBulb = false) {
3679
3840
  this.fixtureService = fixtureService;
3680
3841
  this.previewService = previewService;
3681
3842
  this.pointLightMaxIntensity = 2;
@@ -3686,10 +3847,17 @@ class Fixture3d {
3686
3847
  this.dimmer = 0;
3687
3848
  this.isSelected = false;
3688
3849
  this.isLoaded = false;
3850
+ // spotlight
3689
3851
  this.hasSpotLight = false;
3852
+ // glowing bulb
3853
+ this.hasBulb = false;
3854
+ this.bulbSphereRadius = 3;
3855
+ this.blackColor = new THREE.Color(0x000000);
3690
3856
  this.fixture = fixture;
3691
3857
  this.scene = scene;
3858
+ this.fixtureGroup = fixtureGroup;
3692
3859
  this.hasSpotLight = hasSpotLight;
3860
+ this.hasBulb = hasBulb;
3693
3861
  // evaluate, various capabilities of this fixture
3694
3862
  for (const cachedChannel of this.fixture.channels) {
3695
3863
  if (cachedChannel.channel) {
@@ -3791,6 +3959,15 @@ class Fixture3d {
3791
3959
  spotLightTarget.position.set(0, -10, 0);
3792
3960
  this.createSpotLightBeam();
3793
3961
  }
3962
+ if (this.hasBulb) {
3963
+ const bulbGeo = new THREE.SphereGeometry(this.bulbSphereRadius * 1.12, 64, 64);
3964
+ this.bulbMaterial = new THREE.MeshLambertMaterial({
3965
+ color: 0xff00ff,
3966
+ emissive: 0xff00ff,
3967
+ });
3968
+ this.bulbSphere = new THREE.Mesh(bulbGeo, this.bulbMaterial);
3969
+ this.fixtureGroup.add(this.bulbSphere);
3970
+ }
3794
3971
  }
3795
3972
  getCapabilityInValue(channel, value) {
3796
3973
  for (const capability of channel.capabilities) {
@@ -3801,6 +3978,44 @@ class Fixture3d {
3801
3978
  }
3802
3979
  return undefined;
3803
3980
  }
3981
+ updatePosition(object) {
3982
+ // the offset position (e.g. for single beams inside a fixture)
3983
+ if (object) {
3984
+ object.position.set(this.fixture.pixel ? this.fixture.pixel.positionX : 0, this.fixture.pixel ? this.fixture.pixel.positionY : 0, this.fixture.pixel ? this.fixture.pixel.positionZ : 0);
3985
+ }
3986
+ if (this.hasBulb) {
3987
+ this.bulbSphere.position.set(this.fixture.pixel ? this.fixture.pixel.positionX : 0, (this.fixture.pixel ? this.fixture.pixel.positionY : 0) - 15 + this.bulbSphereRadius / 2, this.fixture.pixel ? this.fixture.pixel.positionZ : 0);
3988
+ }
3989
+ switch (this.fixture.fixture.positioning) {
3990
+ case Positioning.topFront: {
3991
+ this.fixtureGroup.rotation.x = THREE.MathUtils.degToRad(0);
3992
+ this.fixtureGroup.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY - 13, this.fixture.fixture.positionZ);
3993
+ break;
3994
+ }
3995
+ case Positioning.bottomFront: {
3996
+ this.fixtureGroup.rotation.x = THREE.MathUtils.degToRad(180);
3997
+ this.fixtureGroup.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY + 13, this.fixture.fixture.positionZ);
3998
+ break;
3999
+ }
4000
+ case Positioning.topBack: {
4001
+ this.fixtureGroup.rotation.x = THREE.MathUtils.degToRad(0);
4002
+ this.fixtureGroup.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY - 13, this.fixture.fixture.positionZ);
4003
+ break;
4004
+ }
4005
+ case Positioning.bottomBack: {
4006
+ this.fixtureGroup.rotation.x = THREE.MathUtils.degToRad(180);
4007
+ this.fixtureGroup.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY + 13, this.fixture.fixture.positionZ);
4008
+ break;
4009
+ }
4010
+ case Positioning.manual: {
4011
+ this.fixtureGroup.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY, this.fixture.fixture.positionZ);
4012
+ this.fixtureGroup.rotation.x = THREE.MathUtils.degToRad(this.fixture.fixture.rotationX);
4013
+ this.fixtureGroup.rotation.y = THREE.MathUtils.degToRad(this.fixture.fixture.rotationY);
4014
+ this.fixtureGroup.rotation.z = THREE.MathUtils.degToRad(this.fixture.fixture.rotationZ);
4015
+ break;
4016
+ }
4017
+ }
4018
+ }
3804
4019
  // Apply the properties of the base fixture to the preview
3805
4020
  updatePreview(channelValues, masterDimmerValue) {
3806
4021
  // Apply default settings
@@ -3878,37 +4093,11 @@ class Fixture3d {
3878
4093
  this.spotLight.intensity = this.spotLightLightMaxIntensity * this.dimmer;
3879
4094
  this.spotLightBeam.material.uniforms.spotPosition.value = this.spotlightGroup.position;
3880
4095
  }
3881
- }
3882
- updatePosition(object) {
3883
- // Update the position
3884
- switch (this.fixture.fixture.positioning) {
3885
- case Positioning.topFront: {
3886
- object.rotation.x = THREE.MathUtils.degToRad(0);
3887
- object.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY - 13, this.fixture.fixture.positionZ);
3888
- break;
3889
- }
3890
- case Positioning.bottomFront: {
3891
- object.rotation.x = THREE.MathUtils.degToRad(180);
3892
- object.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY + 13, this.fixture.fixture.positionZ);
3893
- break;
3894
- }
3895
- case Positioning.topBack: {
3896
- object.rotation.x = THREE.MathUtils.degToRad(0);
3897
- object.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY - 13, this.fixture.fixture.positionZ);
3898
- break;
3899
- }
3900
- case Positioning.bottomBack: {
3901
- object.rotation.x = THREE.MathUtils.degToRad(180);
3902
- object.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY + 13, this.fixture.fixture.positionZ);
3903
- break;
3904
- }
3905
- case Positioning.manual: {
3906
- object.position.set(this.fixture.fixture.positionX, this.fixture.fixture.positionY, this.fixture.fixture.positionZ);
3907
- object.rotation.x = THREE.MathUtils.degToRad(this.fixture.fixture.rotationX);
3908
- object.rotation.y = THREE.MathUtils.degToRad(this.fixture.fixture.rotationY);
3909
- object.rotation.z = THREE.MathUtils.degToRad(this.fixture.fixture.rotationZ);
3910
- break;
3911
- }
4096
+ if (this.hasBulb) {
4097
+ // Normalize 0–255 → 0–1
4098
+ const intensity = Math.max(this.colorRed, this.colorGreen, this.colorBlue) / 255;
4099
+ this.bulbMaterial.color.copy(color.clone().lerp(this.blackColor, intensity));
4100
+ this.bulbMaterial.emissive.copy(color.clone().lerp(this.blackColor, intensity));
3912
4101
  }
3913
4102
  }
3914
4103
  createSpotLightBeam() {
@@ -3928,12 +4117,15 @@ class Fixture3d {
3928
4117
  if (this.hasSpotLight) {
3929
4118
  this.spotlightMaterial.dispose();
3930
4119
  }
4120
+ if (this.hasBulb) {
4121
+ this.bulbMaterial.dispose();
4122
+ }
3931
4123
  }
3932
4124
  }
3933
4125
 
3934
4126
  class ColorChanger3d extends Fixture3d {
3935
- constructor(fixtureService, previewService, previewMeshService, fixture, scene) {
3936
- super(fixtureService, previewService, fixture, scene, true);
4127
+ constructor(fixtureService, previewService, previewMeshService, fixture, scene, fixtureGroup, hasSpotLight = true, hasBulb = false) {
4128
+ super(fixtureService, previewService, fixture, scene, fixtureGroup, hasSpotLight, hasBulb);
3937
4129
  this.fixtureService = fixtureService;
3938
4130
  this.previewService = previewService;
3939
4131
  this.objectGroup = new THREE.Object3D();
@@ -3947,18 +4139,26 @@ class ColorChanger3d extends Fixture3d {
3947
4139
  createObjects() {
3948
4140
  super.createObjects();
3949
4141
  this.mesh.material = this.previewService.fixtureMaterial;
3950
- this.objectGroup.add(this.spotlightGroup);
4142
+ if (this.hasSpotLight) {
4143
+ this.objectGroup.add(this.spotlightGroup);
4144
+ }
3951
4145
  this.objectGroup.add(this.mesh);
3952
4146
  this.objectGroup.scale.multiplyScalar(9);
3953
- this.scene.add(this.objectGroup);
4147
+ this.fixtureGroup.add(this.objectGroup);
3954
4148
  this.isLoaded = true;
4149
+ this.updatePosition();
4150
+ }
4151
+ updatePosition() {
4152
+ if (!this.isLoaded) {
4153
+ return;
4154
+ }
4155
+ super.updatePosition(this.objectGroup);
3955
4156
  }
3956
4157
  updatePreview(channelValues, masterDimmerValue) {
3957
4158
  if (!this.isLoaded) {
3958
4159
  return;
3959
4160
  }
3960
4161
  super.updatePreview(channelValues, masterDimmerValue);
3961
- this.updatePosition(this.objectGroup);
3962
4162
  // Update the material
3963
4163
  if (this.lastSelected !== this.isSelected) {
3964
4164
  if (this.isSelected) {
@@ -3980,8 +4180,8 @@ class ColorChanger3d extends Fixture3d {
3980
4180
  }
3981
4181
 
3982
4182
  class MovingHead3d extends Fixture3d {
3983
- constructor(fixtureService, previewService, previewMeshService, fixture, scene) {
3984
- super(fixtureService, previewService, fixture, scene, true);
4183
+ constructor(fixtureService, previewService, previewMeshService, fixture, scene, fixtureGroup) {
4184
+ super(fixtureService, previewService, fixture, scene, fixtureGroup, true, true);
3985
4185
  this.fixtureService = fixtureService;
3986
4186
  this.previewService = previewService;
3987
4187
  this.headGroup = new THREE.Object3D();
@@ -4022,15 +4222,21 @@ class MovingHead3d extends Fixture3d {
4022
4222
  this.socket.position.set(0, 1.2, 0);
4023
4223
  // A moving head has about 32 cm in width
4024
4224
  this.objectGroup.scale.multiplyScalar(9);
4025
- this.scene.add(this.objectGroup);
4225
+ this.fixtureGroup.add(this.objectGroup);
4026
4226
  this.isLoaded = true;
4227
+ this.updatePosition();
4228
+ }
4229
+ updatePosition() {
4230
+ if (!this.isLoaded) {
4231
+ return;
4232
+ }
4233
+ super.updatePosition(this.objectGroup);
4027
4234
  }
4028
4235
  updatePreview(channelValues, masterDimmerValue) {
4029
4236
  if (!this.isLoaded) {
4030
4237
  return;
4031
4238
  }
4032
4239
  super.updatePreview(channelValues, masterDimmerValue);
4033
- this.updatePosition(this.objectGroup);
4034
4240
  // Apply default settings
4035
4241
  let panStart = 0;
4036
4242
  let panEnd = 255;
@@ -4101,6 +4307,8 @@ class PreviewComponent {
4101
4307
  this.ngZone = ngZone;
4102
4308
  this.scene = new THREE.Scene();
4103
4309
  this.fixtures3d = [];
4310
+ // grouping fixture (pixels) into the same fixture to being able to properly rotate around the center
4311
+ this.fixtureGroups = new Map();
4104
4312
  this.previewService.doUpdateFixtureSetup.subscribe(() => {
4105
4313
  this.syncFixtures();
4106
4314
  });
@@ -4116,18 +4324,40 @@ class PreviewComponent {
4116
4324
  fixture3d.destroy();
4117
4325
  }
4118
4326
  this.fixtures3d = [];
4327
+ // destroy all groups
4328
+ this.fixtureGroups.clear();
4119
4329
  // add all fixtures from the project
4330
+ const fixture3dMap = {
4331
+ [FixtureCategory['Moving Head']]: MovingHead3d,
4332
+ [FixtureCategory['Color Changer']]: ColorChanger3d,
4333
+ [FixtureCategory['Blinder']]: ColorChanger3d,
4334
+ };
4335
+ // TODO group fixtures of the same UUID but with differen pixels into the same fixture to ensure
4336
+ // rotation is aligned
4120
4337
  for (const fixture of this.fixtureService.cachedFixtures) {
4121
- switch (fixture.profile.categories[0]) {
4122
- case FixtureCategory['Moving Head']:
4123
- this.fixtures3d.push(new MovingHead3d(this.fixtureService, this.previewService, this.previewMeshService, fixture, this.scene));
4124
- break;
4125
- case FixtureCategory['Color Changer']:
4126
- this.fixtures3d.push(new ColorChanger3d(this.fixtureService, this.previewService, this.previewMeshService, fixture, this.scene));
4127
- break;
4128
- case FixtureCategory['Blinder']:
4129
- this.fixtures3d.push(new ColorChanger3d(this.fixtureService, this.previewService, this.previewMeshService, fixture, this.scene));
4130
- break;
4338
+ const category = fixture.profile.categories.find((c) => fixture3dMap[c]);
4339
+ const uuid = fixture.fixture.uuid;
4340
+ let fixtureGroup = this.fixtureGroups.get(uuid);
4341
+ if (!fixtureGroup) {
4342
+ fixtureGroup = new THREE.Group();
4343
+ fixtureGroup.name = `fixture-${uuid}`;
4344
+ this.scene.add(fixtureGroup);
4345
+ this.fixtureGroups.set(uuid, fixtureGroup);
4346
+ }
4347
+ if (category) {
4348
+ const Fixture3dClass = fixture3dMap[category];
4349
+ if (Fixture3dClass === ColorChanger3d) {
4350
+ const isPixelBar = fixture.profile.categories?.[0] === FixtureCategory['Pixel Bar'];
4351
+ const fixture3d = new ColorChanger3d(this.fixtureService, this.previewService, this.previewMeshService, fixture, this.scene, fixtureGroup, !isPixelBar, isPixelBar);
4352
+ this.fixtures3d.push(fixture3d);
4353
+ }
4354
+ else {
4355
+ const fixture3d = new Fixture3dClass(this.fixtureService, this.previewService, this.previewMeshService, fixture, this.scene, fixtureGroup);
4356
+ this.fixtures3d.push(fixture3d);
4357
+ }
4358
+ }
4359
+ else {
4360
+ console.warn(`No supported category found for fixture`);
4131
4361
  }
4132
4362
  }
4133
4363
  }
@@ -4138,22 +4368,28 @@ class PreviewComponent {
4138
4368
  this.renderer.render(this.scene, this.camera);
4139
4369
  }
4140
4370
  updateStagePosition(positioning, xMin, xMax, yMin, yMax, zMin, zMax) {
4141
- let positionCount = 0; // Number of fixtures in the same position
4142
- let positionIndex = 1;
4371
+ let positionCount = 0; // number of fixtures in the same position (only counting one pixel)
4372
+ let positionIndex = 0;
4373
+ let lastFixtureUuid = undefined; // skip new positioning if it's just a new pixel inside the same fixture
4143
4374
  this.projectService.project.presetFixtures.forEach((element, index) => {
4144
4375
  const fixture = this.fixtureService.getFixtureByUuid(element.fixtureUuid);
4145
- if (fixture.positioning === positioning) {
4376
+ if (fixture.positioning === positioning && element.fixtureUuid != lastFixtureUuid) {
4146
4377
  positionCount++;
4147
4378
  }
4379
+ lastFixtureUuid = element.fixtureUuid;
4148
4380
  });
4381
+ lastFixtureUuid = undefined;
4149
4382
  this.projectService.project.presetFixtures.forEach((element, index) => {
4150
4383
  const fixture = this.fixtureService.getFixtureByUuid(element.fixtureUuid);
4151
4384
  if (fixture.positioning === positioning) {
4385
+ if (element.fixtureUuid != lastFixtureUuid) {
4386
+ positionIndex++;
4387
+ }
4152
4388
  fixture.positionX = xMin + ((xMax - xMin) / (positionCount + 1)) * positionIndex;
4153
4389
  fixture.positionY = yMin + ((yMax - yMin) / (positionCount + 1)) * positionIndex;
4154
4390
  fixture.positionZ = zMin + ((zMax - zMin) / (positionCount + 1)) * positionIndex;
4155
- positionIndex++;
4156
4391
  }
4392
+ lastFixtureUuid = element.fixtureUuid;
4157
4393
  });
4158
4394
  }
4159
4395
  animate(timeMillis) {
@@ -4176,6 +4412,10 @@ class PreviewComponent {
4176
4412
  const presets = this.previewService.getPresets(timeMillis);
4177
4413
  const calculatedFixtures = this.previewService.getChannelValues(timeMillis, presets);
4178
4414
  for (const fixture3d of this.fixtures3d) {
4415
+ // Update the fixture positions
4416
+ if (this.previewService.stageAndPositionsDirty) {
4417
+ fixture3d.updatePosition();
4418
+ }
4179
4419
  // Update the fixture properties
4180
4420
  fixture3d.updatePreview(calculatedFixtures.get(fixture3d.fixture) || [], this.projectService.project.masterDimmerValue);
4181
4421
  // Select the fixture, if required
@@ -4183,11 +4423,12 @@ class PreviewComponent {
4183
4423
  fixture3d.isSelected = this.fixtureService.settingsFixtureIsSelected(fixture3d.fixture.fixture);
4184
4424
  }
4185
4425
  else {
4186
- fixture3d.isSelected = this.previewService.fixtureIsSelected(fixture3d.fixture.fixture.uuid, fixture3d.fixture.pixelKey, presets);
4426
+ fixture3d.isSelected = this.previewService.fixtureIsSelected(fixture3d.fixture.fixture.uuid, fixture3d.fixture.pixel?.key, presets);
4187
4427
  }
4188
4428
  }
4189
4429
  // Render the scene
4190
4430
  this.render();
4431
+ this.previewService.stageAndPositionsDirty = false;
4191
4432
  requestAnimationFrame(this.animate.bind(this));
4192
4433
  }
4193
4434
  getAspectRatio() {
@@ -4425,11 +4666,11 @@ class ProjectLoadService {
4425
4666
  projectFixture.fixtureUuid = fixture.uuid;
4426
4667
  this.projectService.project.presetFixtures.push(projectFixture);
4427
4668
  }
4428
- const pixelKeys = this.fixtureService.fixtureGetUniquePixelKeys(fixture);
4429
- for (let pixelKey of pixelKeys) {
4669
+ const pixels = this.fixtureService.fixtureGetUniquePixels(fixture);
4670
+ for (let pixelKey of pixels) {
4430
4671
  const projectFixture = new PresetFixture();
4431
4672
  projectFixture.fixtureUuid = fixture.uuid;
4432
- projectFixture.pixelKey = pixelKey;
4673
+ projectFixture.pixelKey = pixelKey.key;
4433
4674
  this.projectService.project.presetFixtures.push(projectFixture);
4434
4675
  }
4435
4676
  }
@@ -4441,11 +4682,11 @@ class ProjectLoadService {
4441
4682
  presetFixture.fixtureUuid = fixtureUuid;
4442
4683
  preset.fixtures.push(presetFixture);
4443
4684
  }
4444
- const pixelKeys = this.fixtureService.fixtureGetUniquePixelKeys(fixture);
4445
- for (let pixelKey of pixelKeys) {
4685
+ const pixels = this.fixtureService.fixtureGetUniquePixels(fixture);
4686
+ for (let pixel of pixels) {
4446
4687
  const presetFixture = new PresetFixture();
4447
4688
  presetFixture.fixtureUuid = fixtureUuid;
4448
- presetFixture.pixelKey = pixelKey;
4689
+ presetFixture.pixelKey = pixel.key;
4449
4690
  preset.fixtures.push(presetFixture);
4450
4691
  }
4451
4692
  }
@@ -5734,11 +5975,11 @@ class FixturePoolComponent {
5734
5975
  presetFixture.fixtureUuid = fixture.uuid;
5735
5976
  this.projectService.project.presetFixtures.push(presetFixture);
5736
5977
  }
5737
- const pixelKeys = this.fixtureService.fixtureGetUniquePixelKeys(fixture);
5738
- for (let pixelKey of pixelKeys) {
5978
+ const pixels = this.fixtureService.fixtureGetUniquePixels(fixture);
5979
+ for (let pixel of pixels) {
5739
5980
  const presetFixture = new PresetFixture();
5740
5981
  presetFixture.fixtureUuid = fixture.uuid;
5741
- presetFixture.pixelKey = pixelKey;
5982
+ presetFixture.pixelKey = pixel.key;
5742
5983
  this.projectService.project.presetFixtures.push(presetFixture);
5743
5984
  }
5744
5985
  }
@@ -7411,7 +7652,7 @@ class TimelineComponent {
7411
7652
  i0.ɵɵconditional(ctx.timelineService.selectedComposition ? 11 : -1);
7412
7653
  i0.ɵɵadvance();
7413
7654
  i0.ɵɵconditional(ctx.projectService.project.compositions.length == 0 ? 12 : -1);
7414
- } }, dependencies: [i3$2.NgSelectOption, i3$2.ɵNgSelectMultipleOption, i3$2.DefaultValueAccessor, i3$2.SelectControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent, i11.PopoverDirective, i4.TranslatePipe], encapsulation: 2 }); }
7655
+ } }, dependencies: [i3$2.NgSelectOption, i3$2.ɵNgSelectMultipleOption, i3$2.DefaultValueAccessor, i3$2.SelectControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent, i11.PopoverDirective, i4.TranslatePipe], encapsulation: 2 }); }
7415
7656
  }
7416
7657
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TimelineComponent, [{
7417
7658
  type: Component,
@@ -7999,7 +8240,7 @@ class FixtureCapabilityDimmerComponent {
7999
8240
  i0.ɵɵproperty("ngModel", ctx.getValueText() >= 0 ? ctx.getValueText() : 0);
8000
8241
  i0.ɵɵadvance(2);
8001
8242
  i0.ɵɵproperty("value", ctx.getValue() >= 0 ? ctx.getValue() : 0)("orientation", "vertical")("reversed", true)("tooltip", "hide")("min", 0)("max", 1)("step", 0.001);
8002
- } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent], encapsulation: 2 }); }
8243
+ } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent], encapsulation: 2 }); }
8003
8244
  }
8004
8245
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FixtureCapabilityDimmerComponent, [{
8005
8246
  type: Component,
@@ -8110,7 +8351,7 @@ class FixtureCapabilityPanTiltComponent {
8110
8351
  i0.ɵɵproperty("ngModel", ctx.getValueTextTilt() >= 0 ? ctx.getValueTextTilt() : 0);
8111
8352
  i0.ɵɵadvance(2);
8112
8353
  i0.ɵɵproperty("value", ctx.getValueTilt() >= 0 ? ctx.getValueTilt() : 0)("orientation", "vertical")("reversed", true)("tooltip", "hide")("min", 0)("max", 1)("step", 0.001);
8113
- } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent, i4.TranslatePipe], encapsulation: 2 }); }
8354
+ } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent, i4.TranslatePipe], encapsulation: 2 }); }
8114
8355
  }
8115
8356
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FixtureCapabilityPanTiltComponent, [{
8116
8357
  type: Component,
@@ -8547,20 +8788,20 @@ function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template(
8547
8788
  i0.ɵɵelementStart(35, "div", 11)(36, "div", 12);
8548
8789
  i0.ɵɵtext(37, "Y");
8549
8790
  i0.ɵɵelementEnd();
8550
- i0.ɵɵelementStart(38, "div", 13)(39, "mv-slider", 17);
8551
- i0.ɵɵlistener("change", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_mv_slider_change_39_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("z", $event.newValue)); });
8791
+ i0.ɵɵelementStart(38, "div", 13)(39, "mv-slider", 14);
8792
+ i0.ɵɵlistener("change", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_mv_slider_change_39_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("y", $event.newValue)); });
8552
8793
  i0.ɵɵelementEnd()();
8553
8794
  i0.ɵɵelementStart(40, "div", 15)(41, "input", 16);
8554
- i0.ɵɵlistener("ngModelChange", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_input_ngModelChange_41_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("z", $event)); });
8795
+ i0.ɵɵlistener("ngModelChange", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_input_ngModelChange_41_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("y", $event)); });
8555
8796
  i0.ɵɵelementEnd()()();
8556
8797
  i0.ɵɵelementStart(42, "div", 11)(43, "div", 12);
8557
8798
  i0.ɵɵtext(44, "Z");
8558
8799
  i0.ɵɵelementEnd();
8559
- i0.ɵɵelementStart(45, "div", 13)(46, "mv-slider", 14);
8560
- i0.ɵɵlistener("change", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_mv_slider_change_46_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("y", $event.newValue)); });
8800
+ i0.ɵɵelementStart(45, "div", 13)(46, "mv-slider", 17);
8801
+ i0.ɵɵlistener("change", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_mv_slider_change_46_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("z", $event.newValue)); });
8561
8802
  i0.ɵɵelementEnd()();
8562
8803
  i0.ɵɵelementStart(47, "div", 15)(48, "input", 16);
8563
- i0.ɵɵlistener("ngModelChange", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_input_ngModelChange_48_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("y", $event)); });
8804
+ i0.ɵɵlistener("ngModelChange", function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template_input_ngModelChange_48_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.changeRotationManual("z", $event)); });
8564
8805
  i0.ɵɵelementEnd()()()();
8565
8806
  } if (rf & 2) {
8566
8807
  const ctx_r1 = i0.ɵɵnextContext(2);
@@ -8581,17 +8822,17 @@ function FixtureSettingsPositionComponent_Conditional_4_Conditional_19_Template(
8581
8822
  i0.ɵɵadvance(2);
8582
8823
  i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(27, 40, "designer.fixture.setting-rotation"));
8583
8824
  i0.ɵɵadvance(6);
8584
- i0.ɵɵproperty("tooltip", "hide")("value", ctx_r1.rotationX ? ctx_r1.rotationX : (ctx_r1.rotationMin + ctx_r1.rotationMax) / 2)("min", ctx_r1.rotationMin)("max", ctx_r1.rotationMax)("step", 1);
8825
+ i0.ɵɵproperty("tooltip", "hide")("value", ctx_r1.rotationX ? ctx_r1.rotationX : 0)("min", ctx_r1.rotationMin)("max", ctx_r1.rotationMax)("step", 1);
8585
8826
  i0.ɵɵadvance(2);
8586
8827
  i0.ɵɵproperty("ngModel", ctx_r1.rotationX);
8587
8828
  i0.ɵɵadvance(5);
8588
- i0.ɵɵproperty("tooltip", "hide")("value", ctx_r1.rotationZ ? ctx_r1.rotationZ : (ctx_r1.rotationMin + ctx_r1.rotationMax) / 2)("min", ctx_r1.rotationMin)("max", ctx_r1.rotationMax)("step", 1);
8829
+ i0.ɵɵproperty("tooltip", "hide")("value", ctx_r1.rotationY ? ctx_r1.rotationY : 0)("min", ctx_r1.rotationMin)("max", ctx_r1.rotationMax)("step", 1);
8589
8830
  i0.ɵɵadvance(2);
8590
- i0.ɵɵproperty("ngModel", ctx_r1.rotationZ);
8831
+ i0.ɵɵproperty("ngModel", ctx_r1.rotationY);
8591
8832
  i0.ɵɵadvance(5);
8592
- i0.ɵɵproperty("tooltip", "hide")("value", ctx_r1.rotationY ? ctx_r1.rotationY : (ctx_r1.rotationMin + ctx_r1.rotationMax) / 2)("min", ctx_r1.rotationMin)("max", ctx_r1.rotationMax)("step", 1);
8833
+ i0.ɵɵproperty("tooltip", "hide")("value", ctx_r1.rotationZ ? ctx_r1.rotationZ : 0)("min", ctx_r1.rotationMin)("max", ctx_r1.rotationMax)("step", 1);
8593
8834
  i0.ɵɵadvance(2);
8594
- i0.ɵɵproperty("ngModel", ctx_r1.rotationY);
8835
+ i0.ɵɵproperty("ngModel", ctx_r1.rotationZ);
8595
8836
  } }
8596
8837
  function FixtureSettingsPositionComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
8597
8838
  const _r1 = i0.ɵɵgetCurrentView();
@@ -8650,9 +8891,10 @@ function FixtureSettingsPositionComponent_Conditional_5_Template(rf, ctx) { if (
8650
8891
  i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(3, 1, "designer.fixture.no-fixtures-settings-selected"));
8651
8892
  } }
8652
8893
  class FixtureSettingsPositionComponent {
8653
- constructor(fixtureService, presetService) {
8894
+ constructor(fixtureService, presetService, previewService) {
8654
8895
  this.fixtureService = fixtureService;
8655
8896
  this.presetService = presetService;
8897
+ this.previewService = previewService;
8656
8898
  this.selectUndefinedOptionValue = undefined;
8657
8899
  this.selectedPositioning = undefined;
8658
8900
  this.positionX = 0;
@@ -8669,11 +8911,14 @@ class FixtureSettingsPositionComponent {
8669
8911
  this.rotationZ = 0;
8670
8912
  this.rotationMin = 0;
8671
8913
  this.rotationMax = 360;
8672
- presetService.fixtureSelectionSettingsChanged.subscribe(() => {
8914
+ this.presetService.fixtureSelectionSettingsChanged.subscribe(() => {
8673
8915
  this.updateSelection();
8674
8916
  });
8675
8917
  }
8676
8918
  ngOnInit() { }
8919
+ updated() {
8920
+ this.previewService.doUpdateStageAndPositions.next();
8921
+ }
8677
8922
  updateSelection() {
8678
8923
  // get all display values for the selected settings fixtures
8679
8924
  this.selectedPositioning = undefined;
@@ -8766,6 +9011,7 @@ class FixtureSettingsPositionComponent {
8766
9011
  for (const fixture of this.fixtureService.selectedSettingsFixtures) {
8767
9012
  fixture.positioning = positioning;
8768
9013
  }
9014
+ this.updated();
8769
9015
  }
8770
9016
  changePositionManual(position, value) {
8771
9017
  if (isNaN(value)) {
@@ -8803,6 +9049,7 @@ class FixtureSettingsPositionComponent {
8803
9049
  }
8804
9050
  }
8805
9051
  }
9052
+ this.updated();
8806
9053
  }
8807
9054
  changeRotationManual(rotation, value) {
8808
9055
  if (isNaN(value) || value < this.rotationMin || value > this.rotationMax) {
@@ -8828,8 +9075,9 @@ class FixtureSettingsPositionComponent {
8828
9075
  fixture.rotationZ = +value;
8829
9076
  }
8830
9077
  }
9078
+ this.updated();
8831
9079
  }
8832
- static { this.ɵfac = function FixtureSettingsPositionComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FixtureSettingsPositionComponent)(i0.ɵɵdirectiveInject(FixtureService), i0.ɵɵdirectiveInject(PresetService)); }; }
9080
+ static { this.ɵfac = function FixtureSettingsPositionComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FixtureSettingsPositionComponent)(i0.ɵɵdirectiveInject(FixtureService), i0.ɵɵdirectiveInject(PresetService), i0.ɵɵdirectiveInject(PreviewService)); }; }
8833
9081
  static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FixtureSettingsPositionComponent, selectors: [["lib-app-fixture-settings-position"]], standalone: false, decls: 6, vars: 5, consts: [[1, "card", "border-secondary", "h-100"], [1, "card-header"], [1, "card-body", "h-100"], [1, "card-body", "h-100", "d-flex"], [1, "mb-2", 2, "width", "140px", 3, "ngModelChange", "change", "ngModel"], [3, "ngValue"], ["value", "topFront"], ["value", "bottomFront"], ["value", "topBack"], ["value", "bottomBack"], ["value", "manual"], [1, "d-flex"], [1, "my-auto", 2, "width", "20px", "float", "left"], [1, "my-auto", 2, "float", "left"], [2, "width", "80px", 3, "change", "tooltip", "value", "min", "max", "step"], [1, "my-auto"], ["type", "text", 1, "slider-input", 3, "ngModelChange", "ngModel"], [2, "width", "80px", "height", "18px", 3, "change", "tooltip", "value", "min", "max", "step"], [1, "mt-2"], [1, "m-auto", 2, "color", "#717171"]], template: function FixtureSettingsPositionComponent_Template(rf, ctx) { if (rf & 1) {
8834
9082
  i0.ɵɵelementStart(0, "div", 0)(1, "div", 1);
8835
9083
  i0.ɵɵtext(2);
@@ -8845,13 +9093,13 @@ class FixtureSettingsPositionComponent {
8845
9093
  i0.ɵɵconditional(ctx.fixtureService.selectedSettingsFixtures.length > 0 ? 4 : -1);
8846
9094
  i0.ɵɵadvance();
8847
9095
  i0.ɵɵconditional(ctx.fixtureService.selectedSettingsFixtures.length == 0 ? 5 : -1);
8848
- } }, dependencies: [i3$2.NgSelectOption, i3$2.ɵNgSelectMultipleOption, i3$2.DefaultValueAccessor, i3$2.SelectControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent, i4.TranslatePipe], encapsulation: 2 }); }
9096
+ } }, dependencies: [i3$2.NgSelectOption, i3$2.ɵNgSelectMultipleOption, i3$2.DefaultValueAccessor, i3$2.SelectControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent, i4.TranslatePipe], encapsulation: 2 }); }
8849
9097
  }
8850
9098
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FixtureSettingsPositionComponent, [{
8851
9099
  type: Component,
8852
- args: [{ selector: 'lib-app-fixture-settings-position', standalone: false, template: "<div class=\"card border-secondary h-100\">\n <div class=\"card-header\">\n {{ 'designer.fixture.setting-position-title' | translate }}\n </div>\n\n @if (fixtureService.selectedSettingsFixtures.length > 0) {\n <div class=\"card-body h-100\">\n <div>\n <select class=\"mb-2\" style=\"width: 140px\" [(ngModel)]=\"selectedPositioning\" (change)=\"changePosition($event.target.value)\">\n <option [ngValue]=\"selectUndefinedOptionValue\"></option>\n <option value=\"topFront\">{{ 'designer.fixture.setting-position-front-top' | translate }}</option>\n <option value=\"bottomFront\">{{ 'designer.fixture.setting-position-front-bottom' | translate }}</option>\n <option value=\"topBack\">{{ 'designer.fixture.setting-position-back-top' | translate }}</option>\n <option value=\"bottomBack\">{{ 'designer.fixture.setting-position-back-bottom' | translate }}</option>\n <option value=\"manual\">{{ 'designer.fixture.setting-position-manual' | translate }}</option>\n </select>\n </div>\n @if (selectedPositioning == 'manual') {\n <div>\n <div>{{ 'designer.fixture.setting-position' | translate }}</div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">X</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"positionX ? positionX : (positionXMax + positionXMin) / 2\"\n [min]=\"positionXMin\"\n [max]=\"positionXMax\"\n [step]=\"1\"\n (change)=\"changePositionManual('x', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"positionX\" (ngModelChange)=\"changePositionManual('x', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Y</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px; height: 18px\"\n [tooltip]=\"'hide'\"\n [value]=\"positionZ ? positionZ : (positionZMax + positionZMin) / 2\"\n [min]=\"positionZMin\"\n [max]=\"positionZMax\"\n [step]=\"1\"\n (change)=\"changePositionManual('z', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"positionZ\" (ngModelChange)=\"changePositionManual('z', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Z</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"positionY ? positionY : (positionYMax + positionYMin) / 2\"\n [min]=\"positionYMin\"\n [max]=\"positionYMax\"\n [step]=\"1\"\n (change)=\"changePositionManual('y', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"positionY\" (ngModelChange)=\"changePositionManual('y', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"mt-2\">{{ 'designer.fixture.setting-rotation' | translate }}</div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">X</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"rotationX ? rotationX : (rotationMin + rotationMax) / 2\"\n [min]=\"rotationMin\"\n [max]=\"rotationMax\"\n [step]=\"1\"\n (change)=\"changeRotationManual('x', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"rotationX\" (ngModelChange)=\"changeRotationManual('x', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Y</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px; height: 18px\"\n [tooltip]=\"'hide'\"\n [value]=\"rotationZ ? rotationZ : (rotationMin + rotationMax) / 2\"\n [min]=\"rotationMin\"\n [max]=\"rotationMax\"\n [step]=\"1\"\n (change)=\"changeRotationManual('z', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"rotationZ\" (ngModelChange)=\"changeRotationManual('z', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Z</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"rotationY ? rotationY : (rotationMin + rotationMax) / 2\"\n [min]=\"rotationMin\"\n [max]=\"rotationMax\"\n [step]=\"1\"\n (change)=\"changeRotationManual('y', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"rotationY\" (ngModelChange)=\"changeRotationManual('y', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n </div>\n }\n </div>\n } @if (fixtureService.selectedSettingsFixtures.length == 0) {\n <div class=\"card-body h-100 d-flex\">\n <p class=\"m-auto\" style=\"color: #717171\">{{ 'designer.fixture.no-fixtures-settings-selected' | translate }}</p>\n </div>\n }\n</div>\n" }]
8853
- }], () => [{ type: FixtureService }, { type: PresetService }], null); })();
8854
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FixtureSettingsPositionComponent, { className: "FixtureSettingsPositionComponent", filePath: "lib/fixture/fixture-settings/fixture-settings-position/fixture-settings-position.component.ts", lineNumber: 12 }); })();
9100
+ args: [{ selector: 'lib-app-fixture-settings-position', standalone: false, template: "<div class=\"card border-secondary h-100\">\n <div class=\"card-header\">\n {{ 'designer.fixture.setting-position-title' | translate }}\n </div>\n\n @if (fixtureService.selectedSettingsFixtures.length > 0) {\n <div class=\"card-body h-100\">\n <div>\n <select class=\"mb-2\" style=\"width: 140px\" [(ngModel)]=\"selectedPositioning\" (change)=\"changePosition($event.target.value)\">\n <option [ngValue]=\"selectUndefinedOptionValue\"></option>\n <option value=\"topFront\">{{ 'designer.fixture.setting-position-front-top' | translate }}</option>\n <option value=\"bottomFront\">{{ 'designer.fixture.setting-position-front-bottom' | translate }}</option>\n <option value=\"topBack\">{{ 'designer.fixture.setting-position-back-top' | translate }}</option>\n <option value=\"bottomBack\">{{ 'designer.fixture.setting-position-back-bottom' | translate }}</option>\n <option value=\"manual\">{{ 'designer.fixture.setting-position-manual' | translate }}</option>\n </select>\n </div>\n @if (selectedPositioning == 'manual') {\n <div>\n <div>{{ 'designer.fixture.setting-position' | translate }}</div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">X</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"positionX ? positionX : (positionXMax + positionXMin) / 2\"\n [min]=\"positionXMin\"\n [max]=\"positionXMax\"\n [step]=\"1\"\n (change)=\"changePositionManual('x', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"positionX\" (ngModelChange)=\"changePositionManual('x', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Y</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px; height: 18px\"\n [tooltip]=\"'hide'\"\n [value]=\"positionZ ? positionZ : (positionZMax + positionZMin) / 2\"\n [min]=\"positionZMin\"\n [max]=\"positionZMax\"\n [step]=\"1\"\n (change)=\"changePositionManual('z', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"positionZ\" (ngModelChange)=\"changePositionManual('z', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Z</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"positionY ? positionY : (positionYMax + positionYMin) / 2\"\n [min]=\"positionYMin\"\n [max]=\"positionYMax\"\n [step]=\"1\"\n (change)=\"changePositionManual('y', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"positionY\" (ngModelChange)=\"changePositionManual('y', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"mt-2\">{{ 'designer.fixture.setting-rotation' | translate }}</div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">X</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"rotationX ? rotationX : 0\"\n [min]=\"rotationMin\"\n [max]=\"rotationMax\"\n [step]=\"1\"\n (change)=\"changeRotationManual('x', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"rotationX\" (ngModelChange)=\"changeRotationManual('x', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Y</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px\"\n [tooltip]=\"'hide'\"\n [value]=\"rotationY ? rotationY : 0\"\n [min]=\"rotationMin\"\n [max]=\"rotationMax\"\n [step]=\"1\"\n (change)=\"changeRotationManual('y', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"rotationY\" (ngModelChange)=\"changeRotationManual('y', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n <div class=\"d-flex\">\n <div class=\"my-auto\" style=\"width: 20px; float: left\">Z</div>\n <div class=\"my-auto\" style=\"float: left\">\n <mv-slider\n style=\"width: 80px; height: 18px\"\n [tooltip]=\"'hide'\"\n [value]=\"rotationZ ? rotationZ : 0\"\n [min]=\"rotationMin\"\n [max]=\"rotationMax\"\n [step]=\"1\"\n (change)=\"changeRotationManual('z', $event.newValue)\"\n ></mv-slider>\n </div>\n <div class=\"my-auto\">\n <input [ngModel]=\"rotationZ\" (ngModelChange)=\"changeRotationManual('z', $event)\" type=\"text\" class=\"slider-input\" />\n </div>\n </div>\n </div>\n }\n </div>\n } @if (fixtureService.selectedSettingsFixtures.length == 0) {\n <div class=\"card-body h-100 d-flex\">\n <p class=\"m-auto\" style=\"color: #717171\">{{ 'designer.fixture.no-fixtures-settings-selected' | translate }}</p>\n </div>\n }\n</div>\n" }]
9101
+ }], () => [{ type: FixtureService }, { type: PresetService }, { type: PreviewService }], null); })();
9102
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FixtureSettingsPositionComponent, { className: "FixtureSettingsPositionComponent", filePath: "lib/fixture/fixture-settings/fixture-settings-position/fixture-settings-position.component.ts", lineNumber: 13 }); })();
8855
9103
 
8856
9104
  class FixtureSettingsStageComponent {
8857
9105
  constructor(projectService, previewService, changeDetectorRef) {
@@ -8863,6 +9111,7 @@ class FixtureSettingsStageComponent {
8863
9111
  update() {
8864
9112
  this.previewService.updateStage();
8865
9113
  this.changeDetectorRef.detectChanges();
9114
+ this.previewService.doUpdateStageAndPositions.next();
8866
9115
  }
8867
9116
  static { this.ɵfac = function FixtureSettingsStageComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FixtureSettingsStageComponent)(i0.ɵɵdirectiveInject(ProjectService), i0.ɵɵdirectiveInject(PreviewService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); }; }
8868
9117
  static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FixtureSettingsStageComponent, selectors: [["lib-app-fixture-settings-stage"]], standalone: false, decls: 65, vars: 57, consts: [[1, "card", "border-secondary", "h-100"], [1, "card-header"], [1, "card-body", "h-100"], [1, "d-flex"], [1, "my-auto", 2, "width", "120px", "float", "left"], [1, "my-auto", 2, "float", "left"], [2, "width", "140px", 3, "change", "tooltip", "value", "min", "max", "step"], [1, "my-auto"], ["type", "text", "readonly", "", 1, "slider-input-readonly", 3, "ngModelChange", "ngModel"], [1, "my-auto", "ml-1", 2, "color", "darkgray"]], template: function FixtureSettingsStageComponent_Template(rf, ctx) { if (rf & 1) {
@@ -8987,7 +9236,7 @@ class FixtureSettingsStageComponent {
8987
9236
  i0.ɵɵproperty("tooltip", "hide")("value", ctx.projectService.project.stagePillarWidthCm)("min", 1)("max", 200)("step", 5);
8988
9237
  i0.ɵɵadvance(2);
8989
9238
  i0.ɵɵproperty("ngModel", ctx.projectService.project.stagePillarWidthCm);
8990
- } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent, i4.TranslatePipe], encapsulation: 2 }); }
9239
+ } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent, i4.TranslatePipe], encapsulation: 2 }); }
8991
9240
  }
8992
9241
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FixtureSettingsStageComponent, [{
8993
9242
  type: Component,
@@ -9593,7 +9842,7 @@ class EffectCurveComponent {
9593
9842
  i0.ɵɵconditional(ctx.availableProfiles.length > 1 ? 76 : -1);
9594
9843
  i0.ɵɵadvance();
9595
9844
  i0.ɵɵrepeater(i0.ɵɵpipeBind1(79, 81, ctx.availableChannels));
9596
- } }, dependencies: [i3$2.NgSelectOption, i3$2.ɵNgSelectMultipleOption, i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.SelectControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent, i11.PopoverDirective, i9.AsyncPipe, i9.KeyValuePipe, i4.TranslatePipe], styles: [".curve-grid[_ngcontent-%COMP%]{background-color:#000;border:1px solid white}.applying-to[_ngcontent-%COMP%]{overflow-y:scroll;height:130px!important;background-color:#222;border:1px solid white;padding:.5rem}.applying-to[_ngcontent-%COMP%] .form-check[_ngcontent-%COMP%]:last-child{margin-bottom:.5rem}"] }); }
9845
+ } }, dependencies: [i3$2.NgSelectOption, i3$2.ɵNgSelectMultipleOption, i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.SelectControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent, i11.PopoverDirective, i9.AsyncPipe, i9.KeyValuePipe, i4.TranslatePipe], styles: [".curve-grid[_ngcontent-%COMP%]{background-color:#000;border:1px solid white}.applying-to[_ngcontent-%COMP%]{overflow-y:scroll;height:130px!important;background-color:#222;border:1px solid white;padding:.5rem}.applying-to[_ngcontent-%COMP%] .form-check[_ngcontent-%COMP%]:last-child{margin-bottom:.5rem}"] }); }
9597
9846
  }
9598
9847
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EffectCurveComponent, [{
9599
9848
  type: Component,
@@ -10243,7 +10492,7 @@ class MasterDimmerComponent {
10243
10492
  i0.ɵɵproperty("value", ctx.projectService == null ? null : ctx.projectService.project == null ? null : ctx.projectService.project.masterDimmerValue)("orientation", "horizontal")("tooltip", "hide")("min", 0)("max", 1)("step", 0.001);
10244
10493
  i0.ɵɵadvance(2);
10245
10494
  i0.ɵɵproperty("ngModel", ctx.getValue());
10246
- } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent], encapsulation: 2 }); }
10495
+ } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent], encapsulation: 2 }); }
10247
10496
  }
10248
10497
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MasterDimmerComponent, [{
10249
10498
  type: Component,
@@ -10595,7 +10844,7 @@ class FixtureCapabilityChannelComponent {
10595
10844
  i0.ɵɵconditional((ctx._channel.capabilities == null ? null : ctx._channel.capabilities.length) > 1 ? 9 : -1);
10596
10845
  i0.ɵɵadvance();
10597
10846
  i0.ɵɵconditional(ctx.hasRange ? 10 : -1);
10598
- } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.RadioControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i4$1.SliderComponent, i11.PopoverDirective, i4.TranslatePipe], styles: [".color[_ngcontent-%COMP%]{display:inline-block;margin-left:5px;border-radius:50%;width:12px;height:12px;border:1px white solid;vertical-align:middle}"], changeDetection: 0 }); }
10847
+ } }, dependencies: [i3$2.DefaultValueAccessor, i3$2.CheckboxControlValueAccessor, i3$2.RadioControlValueAccessor, i3$2.NgControlStatus, i3$2.NgModel, i3$3.SliderComponent, i11.PopoverDirective, i4.TranslatePipe], styles: [".color[_ngcontent-%COMP%]{display:inline-block;margin-left:5px;border-radius:50%;width:12px;height:12px;border:1px white solid;vertical-align:middle}"], changeDetection: 0 }); }
10599
10848
  }
10600
10849
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FixtureCapabilityChannelComponent, [{
10601
10850
  type: Component,