138 using ChannelMap = ossia::hash_map<QString, Artnet::Channel>;
145 std::vector<Wheel> wheels{};
146 std::vector<FixtureMode> modes{};
148 static std::vector<Wheel> loadWheels(
const rapidjson::Value& value)
noexcept
150 std::vector<Wheel> wheels{};
151 if(!value.IsObject())
154 for(
auto w_it = value.MemberBegin(); w_it != value.MemberEnd(); ++w_it)
156 if(!w_it->value.IsObject())
160 wh.name = w_it->name.GetString();
162 const auto& w = w_it->value.GetObject();
163 if(
auto s_it = w.FindMember(
"slots"); s_it != w.MemberEnd())
165 if(!s_it->value.IsArray())
168 for(
const auto& s : s_it->value.GetArray())
174 if(
auto nm_it = s.FindMember(
"name"); nm_it != s.MemberEnd())
176 if(nm_it->value.IsString())
177 ws.name = nm_it->value.GetString();
179 if(
auto t_it = s.FindMember(
"type"); t_it != s.MemberEnd())
181 if(t_it->value.IsString())
182 ws.type = t_it->value.GetString();
184 wh.wheelslots.push_back(std::move(ws));
188 wheels.push_back(std::move(wh));
195 static std::function<bool(
int)> constraint_pos(std::string_view cst)
noexcept
199 return [](
int p) {
return true; };
202 return [](
int p) {
return (p % 2) == 0; };
203 else if(cst ==
"odd")
204 return [](
int p) {
return (p % 2) == 1; };
205 else if(
auto [whole, n] = leq_rex(cst); whole)
206 return [num = n.to_number()](
int p) {
return p <= num; };
207 else if(
auto [whole, n] = geq_rex(cst); whole)
208 return [num = n.to_number()](
int p) {
return p >= num; };
209 else if(
auto [whole, n] = lst_rex(cst); whole)
210 return [num = n.to_number()](
int p) {
return p < num; };
211 else if(
auto [whole, n] = gst_rex(cst); whole)
212 return [num = n.to_number()](
int p) {
return p > num; };
213 else if(
auto [whole, n] = eq_rex(cst); whole)
214 return [num = n.to_number()](
int p) {
return p == num; };
215 else if(
auto [whole, n, r] = arith_rex(cst); whole)
217 if(
int num = n.to_number(); num > 0)
219 return [num, rem = r.to_number()](
int p) {
return (p % num) == rem; };
222 else if(
auto [whole, n] = arith1_rex(cst); whole)
224 if(
int num = n.to_number(); num > 0)
226 return [num](
int p) {
return (p % num) == 0; };
230 return [](
int p) {
return false; };
233 static std::function<bool(
const QString&)>
234 constraint_name(std::string_view cst)
noexcept
238 return [](
const QString& p) {
return true; };
240 return [rexp = std::make_shared<RE2>(re2::StringPiece(cst.data(), cst.size()))](
241 const QString& p) {
return RE2::FullMatch(p.toStdString(), *rexp); };
244 static std::vector<int> filter_constraints(
245 const std::vector<Pixel>& pixels,
const std::vector<std::string_view>& x_cst,
246 const std::vector<std::string_view>& y_cst,
247 const std::vector<std::string_view>& z_cst,
248 const std::vector<std::string_view>& name_cst)
noexcept
250 std::vector<int> matching;
251 std::vector<std::function<bool(
const Pixel&)>> filters;
254 if(
auto f = constraint_pos(c))
255 filters.push_back([f](
const Pixel& p) {
return f(p.x); });
257 if(
auto f = constraint_pos(c))
258 filters.push_back([f](
const Pixel& p) {
return f(p.y); });
260 if(
auto f = constraint_pos(c))
261 filters.push_back([f](
const Pixel& p) {
return f(p.z); });
262 for(
auto& c : name_cst)
263 if(
auto f = constraint_name(c))
264 filters.push_back([f](
const Pixel& p) {
return f(p.name); });
266 for(
int i = 0; i < pixels.size(); i++)
268 if(pixels[i].name.isEmpty())
271 bool filtered =
false;
272 for(
auto& func : filters)
273 if((filtered = !func(pixels[i])))
277 matching.push_back(i);
283 void loadMatrix(
const rapidjson::Value& mat)
285 using namespace std::literals;
287 if(
auto pk_it = mat.FindMember(
"pixelKeys");
288 pk_it != mat.MemberEnd() && pk_it->value.IsArray())
290 const auto& pk_z = pk_it->value.GetArray();
291 const int16_t dim_z = pk_z.Size();
292 for(int16_t z = 0; z < dim_z; ++z)
294 if(pk_z[z].IsArray())
296 const auto& pk_y = pk_z[z].GetArray();
297 const int16_t dim_y = pk_y.Size();
298 for(int16_t y = 0; y < dim_y; ++y)
300 if(pk_y[y].IsArray())
302 const auto& pk_x = pk_y[y].GetArray();
303 const int16_t dim_x = pk_x.Size();
304 for(int16_t x = 0; x < dim_x; ++x)
306 if(pk_x[x].IsString())
308 matrix.pixels.push_back(
309 Pixel{.x = x, .y = y, .z = z, .name = pk_x[x].GetString()});
317 else if(
auto pc_it = mat.FindMember(
"pixelCount");
318 pc_it != mat.MemberEnd() && pc_it->value.IsArray())
320 const auto& pc = pc_it->value.GetArray();
323 if(!pc[0].IsInt() || !pc[1].IsInt() || !pc[2].IsInt())
325 const int16_t dim_x = pc[0].GetInt();
326 const int16_t dim_y = pc[1].GetInt();
327 const int16_t dim_z = pc[2].GetInt();
329 matrix.pixels.reserve(dim_x * dim_y * dim_z);
331 auto get_name = (dim_y == 1 && dim_z == 1)
332 ? [](
int x,
int y,
int z) {
return QString::number(x + 1); }
335 int z) {
return QString(
"%1 %2").arg(x + 1).arg(y + 1); }
336 : [](
int x,
int y,
int z) {
337 return QString(
"%1 %2 %3").arg(x + 1).arg(y + 1).arg(z + 1);
339 for(int16_t x = 0; x < dim_x; x++)
340 for(int16_t y = 0; y < dim_y; y++)
341 for(int16_t z = 0; z < dim_z; z++)
342 matrix.pixels.push_back(
343 Pixel{.x = x, .y = y, .z = z, .name = get_name(x, y, z)});
346 if(matrix.pixels.empty())
350 if(
auto gg_it = mat.FindMember(
"pixelGroups");
351 gg_it != mat.MemberEnd() && gg_it->value.IsObject())
353 for(
auto g_it = gg_it->value.MemberBegin(); g_it != gg_it->value.MemberEnd();
357 gp.name = g_it->name.GetString();
359 auto& g = g_it->value;
363 if(g.GetString() ==
"all"sv)
366 gp.pixels.reserve(matrix.pixels.size());
367 for(std::size_t i = 0; i < matrix.pixels.size(); i++)
368 gp.pixels.push_back(i);
374 const auto& pxg = g.GetArray();
379 QString px_name = px.GetString();
380 auto px_it = ossia::find_if(
381 matrix.pixels, [=](
auto& pix) { return pix.name == px_name; });
382 if(px_it != matrix.pixels.end())
384 gp.pixels.push_back(std::distance(matrix.pixels.begin(), px_it));
389 else if(g.IsObject())
392 std::vector<std::string_view> x_cst, y_cst, z_cst, name_cst;
393 if(
auto x_cst_it = g.FindMember(
"x");
394 x_cst_it != g.MemberEnd() && x_cst_it->value.IsArray())
396 for(
const auto& v : x_cst_it->value.GetArray())
398 x_cst.push_back(v.GetString());
400 if(
auto y_cst_it = g.FindMember(
"y");
401 y_cst_it != g.MemberEnd() && y_cst_it->value.IsArray())
403 for(
const auto& v : y_cst_it->value.GetArray())
405 y_cst.push_back(v.GetString());
407 if(
auto z_cst_it = g.FindMember(
"z");
408 z_cst_it != g.MemberEnd() && z_cst_it->value.IsArray())
410 for(
const auto& v : z_cst_it->value.GetArray())
412 z_cst.push_back(v.GetString());
414 if(
auto n_cst_it = g.FindMember(
"name");
415 n_cst_it != g.MemberEnd() && n_cst_it->value.IsArray())
417 for(
const auto& v : n_cst_it->value.GetArray())
419 name_cst.push_back(v.GetString());
421 gp.pixels = filter_constraints(matrix.pixels, x_cst, y_cst, z_cst, name_cst);
424 matrix.groups.push_back(std::move(gp));
429 QString getWheelName(QString channelName,
int wheelSlot)
const noexcept
435 for(
auto& wh : this->wheels)
437 if(wh.name == channelName)
439 if(wheelSlot < std::ssize(wh.wheelslots))
441 const auto& w = wh.wheelslots[wheelSlot];
442 if(!w.name.isEmpty())
444 else if(!w.type.isEmpty())
454 ChannelMap loadChannels(
const rapidjson::Value& val)
457 for(
auto chan_it = val.MemberBegin(); chan_it != val.MemberEnd(); ++chan_it)
460 chan.name = chan_it->name.GetString();
461 auto& jchan = chan_it->value;
465 if(
auto default_it = jchan.FindMember(
"defaultValue");
466 default_it != jchan.MemberEnd())
468 if(default_it->value.IsNumber())
470 chan.defaultValue = default_it->value.GetDouble();
472 else if(default_it->value.IsString())
477 std::string_view str = default_it->value.GetString();
479 chan.defaultValue = 127;
483 if(
auto fineChannels_it = jchan.FindMember(
"fineChannelAliases");
484 fineChannels_it != jchan.MemberEnd())
486 const auto& fineChannels = fineChannels_it->value;
487 if(fineChannels.IsArray())
489 const auto& fc = fineChannels.GetArray();
494 chan.fineChannels.push_back(
495 QString::fromUtf8(val.GetString(), val.GetStringLength()));
499 chan.fineChannels.clear();
506 if(
auto capability_it = jchan.FindMember(
"capability");
507 capability_it != jchan.MemberEnd())
510 if(
auto effectname_it = capability_it->value.FindMember(
"effectName");
511 effectname_it != capability_it->value.MemberEnd())
512 cap.effectName = effectname_it->value.GetString();
514 if(
auto comment_it = capability_it->value.FindMember(
"comment");
515 comment_it != capability_it->value.MemberEnd())
516 cap.comment = comment_it->value.GetString();
518 cap.type = capability_it->value[
"type"].GetString();
519 chan.capabilities = std::move(cap);
521 else if(
auto capabilities_it = jchan.FindMember(
"capabilities");
522 capabilities_it != jchan.MemberEnd())
524 std::vector<Artnet::RangeCapability> caps;
525 for(
const auto& capa : capabilities_it->value.GetArray())
527 if(!capa.HasMember(
"type"))
530 QString type = capa[
"type"].GetString();
531 if(type !=
"NoFunction")
534 if(
auto effectname_it = capa.FindMember(
"effectName");
535 effectname_it != capa.MemberEnd())
537 cap.effectName = effectname_it->value.GetString();
539 else if(type.startsWith(
"Wheel"))
541 QString whName = chan.name;
542 if(
auto wh_it = capa.FindMember(
"wheel"); wh_it != capa.MemberEnd())
544 if(wh_it->value.IsString())
545 whName = wh_it->value.GetString();
548 if(
auto idx_it = capa.FindMember(
"slotNumber");
549 idx_it != capa.MemberEnd())
551 if(idx_it->value.IsInt())
553 cap.effectName = getWheelName(whName, idx_it->value.GetInt());
555 else if(idx_it->value.IsDouble())
557 double v = idx_it->value.GetDouble();
558 double lo = std::floor(v);
559 double hi = std::ceil(v);
560 cap.effectName = QString(
"Split %1 %2")
561 .arg(getWheelName(whName, lo))
562 .arg(getWheelName(whName, hi));
567 if(
auto comment_it = capa.FindMember(
"comment");
568 comment_it != capa.MemberEnd())
569 cap.comment = comment_it->value.GetString();
571 cap.type = std::move(type);
573 const auto& range_arr = capa[
"dmxRange"].GetArray();
574 cap.range = {range_arr[0].GetInt(), range_arr[1].GetInt()};
576 caps.push_back(std::move(cap));
580 chan.capabilities = std::move(caps);
584 channels[chan.name] = std::move(chan);
589 void addTemplateToMode(
590 FixtureMode& m,
const rapidjson::Value& repeatFor, std::string_view channelOrder,
591 const std::vector<QString>& templateChannels,
const ChannelMap& templates)
594 if(channelOrder !=
"perPixel")
597 auto addTemplates = [&m, &templates, &templateChannels](
const QString& pixelKey) {
598 for(
const QString& channel : templateChannels)
601 auto template_it = templates.find(channel);
602 if(template_it != templates.end())
604 QString name = template_it->first;
605 name.replace(
"$pixelKey", pixelKey);
608 m.channels.push_back(template_it->second);
609 m.channels.back().name = name;
610 m.allChannels.push_back(name);
615 if(repeatFor.IsString())
617 std::string_view rf = repeatFor.GetString();
618 if(rf ==
"eachPixelABC")
620 auto sortedPixels = this->matrix.pixels;
621 ossia::sort(sortedPixels, [](
const Pixel& p1,
const Pixel& p2) {
622 return p1.name < p2.name;
625 for(
const Pixel& pixel : sortedPixels)
627 addTemplates(pixel.name);
630 else if(rf ==
"eachPixelGroup")
632 for(
const PixelGroup& group : this->matrix.groups)
634 addTemplates(group.name);
637 else if(ossia::string_starts_with(rf,
"eachPixel"))
639 rf = rf.substr(strlen(
"eachPixel"));
642 decltype(&Pixel::x) accessors[3];
643 for(
int i = 0; i < 3; i++)
649 accessors[i] = &Pixel::x;
653 accessors[i] = &Pixel::y;
657 accessors[i] = &Pixel::z;
664 auto sortedPixels = this->matrix.pixels;
665 ossia::sort(sortedPixels, [=](
const Pixel& p1,
const Pixel& p2) {
666 return std::array<int16_t, 3>{
667 p1.*(accessors[2]), p1.*(accessors[1]), p1.*(accessors[0])}
668 < std::array<int16_t, 3>{
669 p2.*(accessors[2]), p2.*(accessors[1]), p2.*(accessors[0])};
672 for(
const Pixel& pixel : sortedPixels)
674 addTemplates(pixel.name);
678 else if(repeatFor.IsArray())
681 for(
const auto& entity_v : repeatFor.GetArray())
683 if(!entity_v.IsString())
686 addTemplates(entity_v.GetString());
691 void loadModes(
const rapidjson::Document& doc)
696 if(
auto it = doc.FindMember(
"wheels"); it != doc.MemberEnd())
697 if(it->value.IsObject())
698 wheels = loadWheels(it->value);
701 if(
auto it = doc.FindMember(
"availableChannels"); it != doc.MemberEnd())
702 if(it->value.IsObject())
703 channels = loadChannels(it->value);
705 ChannelMap templateChannels;
706 if(
auto it = doc.FindMember(
"templateChannels"); it != doc.MemberEnd())
707 if(it->value.IsObject())
708 templateChannels = loadChannels(it->value);
710 if(channels.empty() && templateChannels.empty())
713 if(
auto it = doc.FindMember(
"matrix"); it != doc.MemberEnd())
714 if(it->value.IsObject())
715 loadMatrix(it->value);
717 using namespace std::literals;
719 auto it = doc.FindMember(
"modes");
720 if(it == doc.MemberEnd())
723 if(!it->value.IsArray())
726 for(
auto& mode : it->value.GetArray())
728 auto name_it = mode.FindMember(
"name");
729 auto channels_it = mode.FindMember(
"channels");
730 if(name_it != mode.MemberEnd() && channels_it != mode.MemberEnd())
733 if(name_it->value.IsString())
734 m.name = name_it->value.GetString();
736 if(channels_it->value.IsArray())
738 for(
auto& channel : channels_it->value.GetArray())
740 if(channel.IsString())
742 if(
auto matched_channel_it = channels.find(channel.GetString());
743 matched_channel_it != channels.end())
745 m.channels.push_back(matched_channel_it->second);
751 QString ch = channel.GetString();
752 for(
const auto& tc : templateChannels)
754 for(
const auto& g : this->matrix.groups)
756 QString repl = QString(tc.first).replace(
"$pixelKey", g.name);
759 m.channels.push_back(tc.second);
760 m.channels.back().name = ch;
761 goto could_match_channel;
767 m.allChannels.push_back(
768 QString::fromUtf8(channel.GetString(), channel.GetStringLength()));
770 else if(channel.IsObject())
772 if(
auto insert_it = channel.FindMember(
"insert");
773 insert_it != channel.MemberEnd() && insert_it->value.IsString()
774 && insert_it->value.GetString() ==
"matrixChannels"sv)
776 auto repeatFor_it = channel.FindMember(
"repeatFor");
777 if(repeatFor_it == channel.MemberEnd())
779 auto channelOrder_it = channel.FindMember(
"channelOrder");
780 auto templateChannels_it = channel.FindMember(
"templateChannels");
781 std::string channelOrder = channelOrder_it != channel.MemberEnd()
782 && channelOrder_it->value.IsString()
783 ? channelOrder_it->value.GetString()
786 std::vector<QString> modeChannels;
787 if(templateChannels_it != channel.MemberEnd()
788 && templateChannels_it->value.IsArray())
790 const auto& arr = templateChannels_it->value.GetArray();
794 modeChannels.push_back(c.GetString());
796 modeChannels.push_back({});
801 m, repeatFor_it->value, channelOrder, modeChannels,
807 m.allChannels.push_back({});
811 modes.push_back(std::move(m));
822 std::vector<QString> m_paths;
826 : iterator{std::move(dir), QDirIterator::Subdirectories | QDirIterator::FollowSymlinks}
827 , manufacturer{manufacturer}
830 QDirIterator iterator;
835 : m_paths{fixturesLibraryPaths()}
839 for(
auto& fixtures_dir : m_paths)
841 QFile f{fixtures_dir +
"/manufacturers.json"};
842 if(!f.open(QIODevice::ReadOnly))
844 auto data = f.map(0, f.size());
846 rapidjson::Document doc;
847 doc.Parse(
reinterpret_cast<const char*
>(data), f.size());
848 if(doc.HasParseError())
850 qDebug() <<
"Invalid manufacturers.json !";
854 loadManufacturer(doc, fixtures_dir);
861 void loadManufacturer(rapidjson::Document& doc,
const QString& fixtures_dir)
863 QModelIndex rootIndex;
864 auto manufacturers = readManufacturers(doc);
865 if(m_root.childCount() == 0)
869 for(
auto it = manufacturers.begin(); it != manufacturers.end(); ++it, ++k)
871 beginInsertRows(rootIndex, k, k);
872 auto& child = m_root.emplace_back(FixtureData{it->second}, &m_root);
875 QModelIndex manufacturerIndex = createIndex(k, 0, &child);
877 std::make_shared<Scan>(fixtures_dir +
"/" + it->first, child),
883 for(
auto it = manufacturers.begin(); it != manufacturers.end(); ++it)
885 auto manufacturer_node_it = ossia::find_if(
886 m_root, [&](
const FixtureNode& n) {
return n.name == it->second; });
887 if(manufacturer_node_it == m_root.end())
890 int newRowPosition = 0;
891 auto other_manufacturer_it = m_root.begin();
892 while(other_manufacturer_it != m_root.end()
894 it->second, other_manufacturer_it->name, Qt::CaseInsensitive)
897 other_manufacturer_it++;
901 beginInsertRows(rootIndex, newRowPosition, newRowPosition);
903 = m_root.emplace(other_manufacturer_it, FixtureData{it->second}, &m_root);
906 QModelIndex manufacturerIndex = createIndex(newRowPosition, 0, &child);
908 std::make_shared<Scan>(fixtures_dir +
"/" + it->first, child),
913 int distance = std::abs(std::distance(manufacturer_node_it, m_root.begin()));
914 QModelIndex manufacturerIndex
915 = createIndex(distance, 0, &*manufacturer_node_it);
917 std::make_shared<Scan>(
918 fixtures_dir +
"/" + it->first, *manufacturer_node_it),
926 std::string_view fixture_data, FixtureNode& manufacturer,
927 const QModelIndex& manufacturerIndex)
929 rapidjson::Document doc;
930 doc.Parse(fixture_data.data(), fixture_data.size());
931 if(doc.HasParseError())
933 qDebug() <<
"Invalid JSON document !";
936 if(
auto it = doc.FindMember(
"name"); it != doc.MemberEnd())
938 QString name = it->value.GetString();
940 int newRowPosition = 0;
941 auto other_fixture_it = manufacturer.begin();
942 while(other_fixture_it != manufacturer.end()
943 && QString::compare(name, other_fixture_it->name, Qt::CaseInsensitive) >= 0)
949 beginInsertRows(manufacturerIndex, newRowPosition, newRowPosition);
951 = manufacturer.emplace(other_fixture_it, FixtureData{name}, &manufacturer);
956 if(
auto it = doc.FindMember(
"categories"); it != doc.MemberEnd())
958 for(
auto& category : it->value.GetArray())
960 data.tags.push_back(category.GetString());
968 void nextFixture(std::shared_ptr<Scan> scan, QModelIndex manufacturerIndex)
970 auto& iterator = scan->iterator;
971 if(iterator.hasNext())
973 const auto filepath = iterator.next();
974 if(QFileInfo fi{filepath}; fi.suffix() ==
"json")
976 const std::string_view req{
"fixture.json"};
977 score::findStringInFile(filepath, req, [&](QFile& f) {
978 unsigned char* data = f.map(0, f.size());
980 const char* cbegin =
reinterpret_cast<char*
>(data);
983 std::string_view(cbegin, f.size()), scan->manufacturer, manufacturerIndex);
989 1,
this, [
this, scan = std::move(scan), idx = manufacturerIndex]()
mutable {
990 nextFixture(std::move(scan), idx);
996 FixtureNode& rootNode()
override {
return m_root; }
998 const FixtureNode& rootNode()
const override {
return m_root; }
1000 int columnCount(
const QModelIndex& parent)
const override {
return 2; }
1002 QVariant data(
const QModelIndex& index,
int role)
const override
1004 const auto& node = nodeFromModelIndex(index);
1005 if(index.column() == 0)
1009 case Qt::DisplayRole:
1015 else if(index.column() == 1)
1019 case Qt::DisplayRole:
1020 return node.tags.join(
", ");
1026 QVariant headerData(
int section, Qt::Orientation orientation,
int role)
const override
1028 if(role != Qt::DisplayRole)
1033 return tr(
"Fixture");
1040 Qt::ItemFlags flags(
const QModelIndex& index)
const override
1044 f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1049 QModelIndex modelIndexFromNode(
const FixtureNode& n)
const
1051 node_type* parent = n.parent();
1052 SCORE_ASSERT(parent);
1053 SCORE_ASSERT(parent != &rootNode());
1055 return createIndex(parent->indexOfChild(&n), 0, &n);
1058 static FixtureDatabase& instance()
1060 static FixtureDatabase db;
1064 void onPopulated(
auto func)
1072 QTimer::singleShot(8,
this, [
this, func = std::move(func)]()
mutable {
1073 onPopulated(std::move(func));