SPH
NodePage.cpp
Go to the documentation of this file.
1 #include "gui/windows/NodePage.h"
2 #include "gui/Utils.h"
10 #include "io/FileSystem.h"
12 #include "run/Config.h"
13 #include "run/ScriptNode.h"
14 #include "run/SpecialEntries.h"
15 #include "run/jobs/IoJobs.h"
16 #include "run/jobs/Presets.h"
17 #include "run/jobs/ScriptJobs.h"
18 #include "thread/CheckFunction.h"
19 #include <wx/dcclient.h>
20 #include <wx/dirdlg.h>
21 #include <wx/graphics.h>
22 #include <wx/menu.h>
23 #include <wx/msgdlg.h>
24 #include <wx/richtooltip.h>
25 #include <wx/settings.h>
26 #include <wx/sizer.h>
27 #include <wx/stattext.h>
28 #include <wx/treectrl.h>
29 
30 #include <wx/propgrid/propgrid.h>
31 #include <wx/propgrid/props.h>
32 // has to be included last
33 #include <wx/propgrid/advprops.h>
34 
35 #include <wx/aui/framemanager.h>
36 
38 
39 constexpr int FIRST_SLOT_Y = 60;
40 constexpr int SLOT_DY = 25;
41 constexpr int SLOT_RADIUS = 6;
42 
44 static AnimationJob animationDummy("dummy");
45 static PerspectiveCameraJob cameraDummy("dummy");
46 
47 #ifdef SPH_USE_CHAISCRIPT
48 static ChaiScriptJob scriptDummy("dummy");
49 #endif
50 
51 //-----------------------------------------------------------------------------------------------------------
52 // NodeManager
53 //-----------------------------------------------------------------------------------------------------------
54 
56  : editor(editor)
57  , callbacks(callbacks) {
67  .set(RunSettingsId::RUN_AUTHOR, std::string("Pavel Sevecek"))
68  .set(RunSettingsId::RUN_COMMENT, std::string(""))
69  .set(RunSettingsId::RUN_EMAIL, std::string("sevecek@sirrah.troja.mff.cuni.cz"));
70 }
71 
73  const std::string currentName = node->instanceName();
74  UniqueNameManager nameMgr = this->makeUniqueNameManager();
75  const std::string fixedName = nameMgr.getName(currentName);
76  if (fixedName != currentName) {
77  VirtualSettings settings = node->getSettings();
78  settings.set("name", fixedName);
79  }
80 
81  VisNode vis(node.get(), position);
82  VisNode& stored = nodes.insert(node, vis);
83  editor->Refresh();
84  return &stored;
85 }
86 
88  const wxSize size = editor->GetSize();
89  return this->addNode(node, Pixel(size.x / 2, size.y / 2) - editor->offset());
90 }
91 
93  node.enumerate([this](SharedPtr<JobNode> node, Size UNUSED(depth)) {
94  nodes.insert(node, VisNode(node.get(), Pixel(0, 0)));
95  });
96  this->layoutNodes(node, Pixel(800, 200) - editor->offset());
97  callbacks->markUnsaved(true);
98 }
99 
102  const wxSize size = editor->GetSize();
103  const Pixel pivot = Pixel(size.x / 2, size.y / 2) - editor->offset();
104  const Pixel offset = pivot - nodes[node.sharedFromThis()].position;
105 
106  SharedPtr<JobNode> clonedRoot = Sph::cloneHierarchy(node);
107  this->addNodes(*clonedRoot);
108 
109  // fix positions
110  Array<SharedPtr<JobNode>> origTree, clonedTree;
111  node.enumerate([&origTree](SharedPtr<JobNode> node) { //
112  origTree.push(node);
113  });
114  clonedRoot->enumerate([&clonedTree](SharedPtr<JobNode> node) { //
115  clonedTree.push(node);
116  });
117  SPH_ASSERT(origTree.size() == clonedTree.size());
118 
119  for (Size i = 0; i < origTree.size(); ++i) {
120  nodes[clonedTree[i]].position = nodes[origTree[i]].position + offset;
121  }
122 }
123 
126 
127  node.enumerate([&depthMap](SharedPtr<JobNode> node, Size depth) { //
128  depthMap.insert(node, depth);
129  });
130 
131  // fix depth map so that each provider is at least +1 in depth
132  bool depthChanged;
133  do {
134  depthChanged = false;
135  for (auto& element : depthMap) {
136  SharedPtr<JobNode> node = element.key;
137 
138  // find depths of providers
139  for (Size i = 0; i < node->getSlotCnt(); ++i) {
140  SlotData data = node->getSlot(i);
141  if (data.provider && depthMap[data.provider] <= depthMap[node]) {
142  depthMap[data.provider]++;
143  depthChanged = true;
144  }
145  }
146  }
147  } while (depthChanged);
148 
149 
151  for (auto& element : depthMap) {
152  const int depth = element.value;
153  if (!depthMapInv.contains(depth)) {
154  depthMapInv.insert(depth, {});
155  }
156  depthMapInv[depth].push(element.key);
157  }
158 
159  for (auto& element : depthMapInv) {
160  const int depth = element.key;
161  int index = 0;
162  for (auto& node : element.value) {
163  VisNode& vis = nodes[node];
164  vis.position =
165  Pixel(position.x - 200 * depth, position.y + 150 * index - (element.value.size() - 1) * 75);
166  ++index;
167  }
168  }
169 
170  editor->Refresh();
171  callbacks->markUnsaved(true);
172 }
173 
175  for (Size i = 0; i < node.getSlotCnt(); ++i) {
176  if (SharedPtr<JobNode> provider = node.getSlot(i).provider) {
177  provider->disconnect(node.sharedFromThis());
178  }
179  }
180  node.disconnectAll();
181  nodes.remove(node.sharedFromThis());
182  callbacks->markUnsaved(true);
183 }
184 
186  Array<SharedPtr<JobNode>> toRemove;
187  node.enumerate([&toRemove](SharedPtr<JobNode> node) { toRemove.push(node); });
188  for (SharedPtr<JobNode> n : toRemove) {
189  nodes.remove(n);
190  }
191  callbacks->markUnsaved(true);
192 }
193 
195  nodes.clear();
196  editor->Refresh();
197  callbacks->markUnsaved(true);
198 }
199 
201  // Nodes are drawn in linear order, meaning nodes in the back will be higher in z-order than nodes in the
202  // front. To pick the uppermost one, just iterate in reverse.
203  for (auto& element : reverse(nodes)) {
204  VisNode& node = element.value;
205  wxRect rect(wxPoint(node.position), wxPoint(node.position + node.size()));
206  if (rect.Contains(wxPoint(position))) {
207  return &node;
208  }
209  }
210  return nullptr;
211 }
212 
214  for (auto& element : nodes) {
215  VisNode& node = element.value;
216  const Pixel relative = position - node.position;
217  for (Size i = 0; i < node.node->getSlotCnt(); ++i) {
218  const float dist = getLength(relative - Pixel(0, FIRST_SLOT_Y + i * SLOT_DY));
219  if (dist < SLOT_RADIUS) {
220  return { &node, i };
221  }
222  }
223 
224  const float dist = getLength(relative - Pixel(node.size().x, FIRST_SLOT_Y));
225  if (dist < SLOT_RADIUS) {
226  return { &node, NodeSlot::RESULT_SLOT };
227  }
228  }
229  return { nullptr, 0 };
230 }
231 
233 private:
234  ConfigNode& out;
235 
236 public:
237  explicit SaveProc(ConfigNode& out)
238  : out(out) {}
239 
240  virtual void onCategory(const std::string& UNUSED(name)) const override {
241  // do nothing
242  }
243 
244  virtual void onEntry(const std::string& name, IVirtualEntry& entry) const override {
245  const IVirtualEntry::Type type = entry.getType();
246  switch (type) {
248  out.set<bool>(name, entry.get());
249  break;
251  out.set<int>(name, entry.get());
252  break;
254  out.set<Float>(name, entry.get());
255  break;
257  out.set<Vector>(name, entry.get());
258  break;
260  out.set<Interval>(name, entry.get());
261  break;
263  out.set<std::string>(name, entry.get());
264  break;
266  out.set<Path>(name, entry.get());
267  break;
270  EnumWrapper ew = entry.get();
271  out.set<int>(name, ew.value);
272  break;
273  }
275  ExtraEntry extra(entry.get());
276  out.set<std::string>(name, extra.toString());
277  break;
278  }
279  default:
281  }
282  }
283 };
284 
285 void NodeManager::save(Config& config) {
287 
288  try {
289  SharedPtr<ConfigNode> outGlobals = config.addNode("globals");
290  VirtualSettings globalSettings = getGlobalSettings();
291  globalSettings.enumerate(SaveProc(*outGlobals));
292 
293  SharedPtr<ConfigNode> outNodes = config.addNode("nodes");
294  for (auto& element : nodes) {
295  const SharedPtr<JobNode> node = element.key;
296  const VisNode vis = element.value;
297 
298  SharedPtr<ConfigNode> out = outNodes->addChild(node->instanceName());
299 
300  out->set("class_name", node->className());
301  out->set("position", vis.position);
302 
303  // save connected slots
304  for (Size i = 0; i < node->getSlotCnt(); ++i) {
305  SlotData slot = node->getSlot(i);
306  if (SharedPtr<JobNode> provider = slot.provider) {
307  out->set(slot.name, provider->instanceName());
308  }
309  }
310 
311  VirtualSettings settings = node->getSettings();
312  settings.enumerate(SaveProc(*out));
313  }
314 
315  batch.save(config);
316 
317  } catch (const Exception& e) {
318  wxMessageBox(std::string("Cannot save file.\n\n") + e.what(), "Error", wxOK);
319  }
320 }
321 
322 class LoadProc : public VirtualSettings::IEntryProc {
323 private:
324  ConfigNode& input;
325 
326 public:
328  : input(input) {}
329 
330  virtual void onCategory(const std::string& UNUSED(name)) const override {}
331 
332  virtual void onEntry(const std::string& name, IVirtualEntry& entry) const override {
333  const IVirtualEntry::Type type = entry.getType();
334 
335  try {
336  switch (type) {
338  entry.set(input.get<bool>(name));
339  break;
341  entry.set(input.get<int>(name));
342  break;
344  entry.set(input.get<Float>(name));
345  break;
347  entry.set(input.get<Vector>(name));
348  break;
350  entry.set(input.get<Interval>(name));
351  break;
353  entry.set(input.get<std::string>(name));
354  break;
356  entry.set(input.get<Path>(name));
357  break;
360  EnumWrapper ew = entry.get();
361  ew.value = input.get<int>(name);
362  entry.set(ew);
363  break;
364  }
367  ExtraEntry extra(makeAuto<CurveEntry>());
368  extra.fromString(input.get<std::string>(name));
369  entry.set(extra);
370  break;
371  }
372  default:
374  }
375  } catch (const Exception& e) {
377  std::cout << "Failed to load value, keeping the default.\n" << e.what() << std::endl;
378  }
379  }
380 };
381 
382 
383 void NodeManager::load(Config& config) {
385 
386  nodes.clear();
387 
388  try {
389  SharedPtr<ConfigNode> inGlobals = config.getNode("globals");
390  VirtualSettings globalSettings = this->getGlobalSettings();
391  globalSettings.enumerate(LoadProc(*inGlobals));
392 
393  SharedPtr<ConfigNode> inNodes = config.getNode("nodes");
394  Array<Tuple<SharedPtr<JobNode>, std::string, std::string>> allToConnect;
395  inNodes->enumerateChildren([this, &allToConnect](std::string name, ConfigNode& input) {
396  RawPtr<IJobDesc> desc;
397  try {
398  const std::string className = input.get<std::string>("class_name");
399  desc = getJobDesc(className);
400  if (!desc) {
401  throw Exception("cannot find desc for node '" + className + "'");
402  }
403  } catch (const Exception& e) {
404  wxMessageBox(e.what(), "Error", wxOK);
405  return;
406  }
407 
408  SharedPtr<JobNode> node = makeShared<JobNode>(desc->create(name));
409  this->addNode(node, input.get<Pixel>("position"));
410  VirtualSettings settings = node->getSettings();
411  settings.enumerate(LoadProc(input));
412 
413  for (Size i = 0; i < node->getSlotCnt(); ++i) {
414  const std::string slotName = node->getSlot(i).name;
415  Optional<std::string> connectedName = input.tryGet<std::string>(slotName);
416  if (connectedName) {
417  allToConnect.push(makeTuple(node, slotName, connectedName.value()));
418  }
419  }
420  });
421 
422  for (auto& toConnect : allToConnect) {
423  for (auto& element : nodes) {
424  if (element.key->instanceName() == toConnect.get<2>()) {
425  element.key->connect(toConnect.get<0>(), toConnect.get<1>());
426  }
427  }
428  }
429 
430  Array<SharedPtr<JobNode>> nodeList;
431  for (auto& pair : nodes) {
432  nodeList.push(pair.key);
433  }
434  batch.load(config, nodeList);
435 
436  } catch (const Exception& e) {
437  wxMessageBox(std::string("Cannot load file.\n\n") + e.what(), "Error", wxOK);
438  }
439 }
440 
442  // clone all nodes to avoid touching the data while the simulation is running
443  callbacks->startRun(Sph::cloneHierarchy(node, std::string("")), globals, node.instanceName());
444 }
445 
447 class BatchJob : public IParticleJob {
448 private:
449  Size runCnt;
450 
451 public:
452  BatchJob(const std::string& name, const Size runCnt)
453  : IParticleJob(name)
454  , runCnt(runCnt) {}
455 
456  virtual std::string className() const override {
457  return "batch run";
458  }
459 
462  for (Size i = 0; i < runCnt; ++i) {
463  map.insert("job " + std::to_string(i), JobType::PARTICLES);
464  }
465  return map;
466  }
467 
468  virtual VirtualSettings getSettings() override {
470  }
471 
472  virtual void evaluate(const RunSettings& UNUSED(global), IRunCallbacks& UNUSED(callbacks)) override {
473  // only used for run the dependencies, the job itself is empty
474  }
475 };
476 
478  RawPtr<IJobDesc> desc = getJobDesc(node.className());
479  SPH_ASSERT(desc);
480 
481  // validate
482  for (Size col = 0; col < batch.getParamCount(); ++col) {
483  if (!batch.getParamNode(col)) {
484  wxMessageBox(std::string(
485  "Incomplete set up of batch run.\nSet up all parameters in Project / Batch Run."));
486  return;
487  }
488  }
489 
490  Array<SharedPtr<JobNode>> batchNodes;
491  try {
492  for (Size runIdx = 0; runIdx < batch.getRunCount(); ++runIdx) {
493  SharedPtr<JobNode> runNode = Sph::cloneHierarchy(node, batch.getRunName(runIdx) + " / ");
494  batch.modifyHierarchy(runIdx, *runNode);
495  batchNodes.push(runNode);
496  }
497  } catch (const Exception& e) {
498  wxMessageBox(std::string("Cannot start batch run.\n\n") + e.what(), "Error", wxOK);
499  }
500 
501  SharedPtr<JobNode> root = makeNode<BatchJob>("batch", batchNodes.size());
502  for (Size i = 0; i < batchNodes.size(); ++i) {
503  batchNodes[i]->connect(root, "job " + std::to_string(i));
504  }
505 
506  callbacks->startRun(root, globals, root->instanceName());
507 }
508 
509 void NodeManager::startScript(const Path& file) {
510 #ifdef SPH_USE_CHAISCRIPT
511  Array<SharedPtr<JobNode>> rootNodes = getRootNodes();
512  Array<SharedPtr<JobNode>> clonedNodes;
513  for (const auto& node : rootNodes) {
514  SharedPtr<JobNode> cloned = Sph::cloneHierarchy(*node, std::string());
515  cloned->enumerate([&](SharedPtr<JobNode> job) { clonedNodes.push(job); });
516  }
517  SharedPtr<ScriptNode> node = makeShared<ScriptNode>(file, std::move(clonedNodes));
518 
519  callbacks->startRun(node, globals, "Script '" + file.native() + "'");
520 #else
521  throw InvalidSetup("Cannot start script '" + file.native() + "', no ChaiScript support.");
522 #endif
523 }
524 
526  Array<SharedPtr<JobNode>> inputs;
527  for (auto& element : nodes) {
528  SharedPtr<JobNode> node = element.key;
529  Optional<ExtJobType> provided = node->provides();
530  if ((!provided || provided.value() == JobType::PARTICLES) && node->getDependentCnt() == 0) {
531  inputs.push(Sph::cloneHierarchy(*node, std::string("")));
532  }
533  }
534  if (inputs.empty()) {
535  wxMessageBox("No simulations to start.");
536  return;
537  }
538 
539  SharedPtr<JobNode> root = makeNode<BatchJob>("batch", inputs.size());
540  for (Size i = 0; i < inputs.size(); ++i) {
541  inputs[i]->connect(root, "job " + std::to_string(i));
542  }
543 
544  callbacks->startRun(root, globals, root->instanceName());
545 }
546 
548  Array<SharedPtr<JobNode>> inputs;
549  for (auto& element : nodes) {
550  SharedPtr<JobNode> node = element.key;
551  Optional<ExtJobType> provided = node->provides();
552  if ((!provided || provided.value() == JobType::PARTICLES) && node->getDependentCnt() == 0) {
553  inputs.push(node);
554  }
555  }
556  return inputs;
557 }
558 
560  VirtualSettings settings;
561 
562  VirtualSettings::Category& sphCat = settings.addCategory("SPH parameters");
563  sphCat.connect<EnumWrapper>("SPH kernel", globals, RunSettingsId::SPH_KERNEL);
564 
565  VirtualSettings::Category& parallelCat = settings.addCategory("Parallelization");
566  parallelCat.connect<int>("Number of threads", globals, RunSettingsId::RUN_THREAD_CNT);
567  parallelCat.connect<int>("Particle granularity", globals, RunSettingsId::RUN_THREAD_GRANULARITY);
568  parallelCat.connect<int>("K-d tree leaf size", globals, RunSettingsId::FINDER_LEAF_SIZE);
569  parallelCat.connect<int>("Max parallel depth", globals, RunSettingsId::FINDER_MAX_PARALLEL_DEPTH);
570 
571  VirtualSettings::Category& flawCat = settings.addCategory("Random numbers");
572  flawCat.connect<EnumWrapper>("Random-number generator", globals, RunSettingsId::RUN_RNG);
573  flawCat.connect<int>("Random seed", globals, RunSettingsId::RUN_RNG_SEED);
574 
575  VirtualSettings::Category& renderCat = settings.addCategory("Rendering");
576  renderCat.connect<bool>("Enable textures", globals, RunSettingsId::GENERATE_UVWS);
577  renderCat.connect<EnumWrapper>("UV mapping", globals, RunSettingsId::UVW_MAPPING);
578 
579  VirtualSettings::Category& authorCat = settings.addCategory("Run metadata");
580  authorCat.connect<std::string>("Author name", globals, RunSettingsId::RUN_AUTHOR);
581  authorCat.connect<std::string>("Author e-mail", globals, RunSettingsId::RUN_EMAIL);
582  authorCat.connect<std::string>("Comment", globals, RunSettingsId::RUN_COMMENT);
583 
584  return settings;
585 }
586 
588  Array<std::string> names;
589  for (auto& element : nodes) {
590  names.push(element.key->instanceName());
591  }
592 
593  UniqueNameManager uniqueNames(names);
594  return uniqueNames;
595 }
596 
598  Array<SharedPtr<JobNode>> nodeList;
599  for (auto& pair : nodes) {
600  nodeList.push(pair.key);
601  }
602  BatchDialog* batchDialog = new BatchDialog(editor, batch, std::move(nodeList));
603  if (batchDialog->ShowModal() == wxID_OK) {
604  batch = batchDialog->getBatch().clone();
605  callbacks->markUnsaved(true);
606  }
607  batchDialog->Destroy();
608 }
609 
611  return alignedNew<RenderPane>(parent, wxDefaultSize, node.sharedFromThis(), globals);
612 }
613 
615  SharedPtr<JobNode> node = activeNode.lock();
616  if (node) {
617  callbacks->startRun(node, globals, node->instanceName());
618  return;
619  }
620 
622  if (nodeList.empty()) {
623  wxMessageBox(std::string("No simulation nodes added. First, create a simulation by double-clicking "
624  "an item in the node list on the right side."),
625  "No runs",
626  wxOK);
627  return;
628  }
629 
630  if (nodeList.size() == 1) {
631  // only a single node, no need for run select dialog
632  SharedPtr<JobNode> node = nodeList.front();
633  callbacks->startRun(node, globals, node->instanceName());
634  return;
635  }
636 
637  RunSelectDialog* dialog = new RunSelectDialog(editor, std::move(nodeList));
638  if (dialog->ShowModal() == wxID_OK) {
639  node = dialog->selectedNode();
640  if (dialog->remember()) {
641  activeNode = node;
642  }
643  callbacks->startRun(node, globals, node->instanceName());
644  }
645  dialog->Destroy();
646 }
647 
648 //-----------------------------------------------------------------------------------------------------------
649 // NodeEditor
650 //-----------------------------------------------------------------------------------------------------------
651 
653  : wxPanel(parent, wxID_ANY)
654  , callbacks(callbacks)
655  , nodeWindow(parent) {
656  this->SetMinSize(wxSize(1024, 768));
657 
658  this->Connect(wxEVT_PAINT, wxPaintEventHandler(NodeEditor::onPaint));
659  this->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(NodeEditor::onMouseWheel));
660  this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(NodeEditor::onLeftDown));
661  this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(NodeEditor::onLeftUp));
662  this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(NodeEditor::onRightUp));
663  this->Connect(wxEVT_MOTION, wxMouseEventHandler(NodeEditor::onMouseMotion));
664  this->Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(NodeEditor::onDoubleClick));
665 }
666 
667 static void drawCenteredText(wxGraphicsContext* gc,
668  const std::string& text,
669  const Pixel from,
670  const Pixel to) {
671  wxDouble width, height, descent, externalLeading;
672  gc->GetTextExtent(text, &width, &height, &descent, &externalLeading);
673  const Pixel pivot = (from + to) / 2 - Pixel(int(width), int(height)) / 2;
674  gc->DrawText(text, pivot.x, pivot.y);
675 }
676 
678  if (index == RESULT_SLOT) {
680  } else {
681  return vis->position + Pixel(0, FIRST_SLOT_Y + SLOT_DY * index);
682  }
683 }
684 
685 
686 static wxColour getLineColor(const Rgba& background) {
687  if (background.intensity() > 0.5f) {
688  // light theme
689  return wxColour(30, 30, 30);
690  } else {
691  // dark theme
692  return wxColour(230, 230, 230);
693  }
694 }
695 
696 static FlatMap<ExtJobType, wxPen> NODE_PENS_DARK = [] {
698  wxPen& storagePen = pens.insert(JobType::PARTICLES, *wxBLACK_PEN);
699  storagePen.SetColour(wxColour(230, 230, 230));
700 
701  wxPen& materialPen = pens.insert(JobType::MATERIAL, *wxBLACK_PEN);
702  materialPen.SetColour(wxColour(255, 120, 60));
703  materialPen.SetStyle(wxPENSTYLE_SHORT_DASH);
704 
705  wxPen& geometryPen = pens.insert(JobType::GEOMETRY, *wxBLACK_PEN);
706  geometryPen.SetColour(wxColour(60, 120, 255));
707  geometryPen.SetStyle(wxPENSTYLE_SHORT_DASH);
708 
709  wxPen& cameraPen = pens.insert(GuiJobType::CAMERA, *wxBLACK_PEN);
710  cameraPen.SetColour(wxColour(150, 225, 100));
711  cameraPen.SetStyle(wxPENSTYLE_SHORT_DASH);
712  return pens;
713 }();
714 
715 static FlatMap<ExtJobType, wxPen> NODE_PENS_LIGHT = [] {
717  wxPen& storagePen = pens.insert(JobType::PARTICLES, *wxBLACK_PEN);
718  storagePen.SetColour(wxColour(30, 30, 30));
719 
720  wxPen& materialPen = pens.insert(JobType::MATERIAL, *wxBLACK_PEN);
721  materialPen.SetColour(wxColour(150, 40, 10));
722  materialPen.SetStyle(wxPENSTYLE_SHORT_DASH);
723 
724  wxPen& geometryPen = pens.insert(JobType::GEOMETRY, *wxBLACK_PEN);
725  geometryPen.SetColour(wxColour(0, 20, 80));
726  geometryPen.SetStyle(wxPENSTYLE_SHORT_DASH);
727 
728  wxPen& cameraPen = pens.insert(GuiJobType::CAMERA, *wxBLACK_PEN);
729  cameraPen.SetColour(wxColour(80, 10, 10));
730  cameraPen.SetStyle(wxPENSTYLE_SHORT_DASH);
731  return pens;
732 }();
733 
734 static wxPen& getNodePen(const ExtJobType type, const bool isLightTheme) {
735  if (isLightTheme) {
736  return NODE_PENS_LIGHT[type];
737  } else {
738  return NODE_PENS_DARK[type];
739  }
740 }
741 
742 static Rgba decreaseContrast(const Rgba& color, const float amount, const bool darken) {
743  if (darken) {
744  return color.darken(amount);
745  } else {
746  return color.brighten(3.f * amount);
747  }
748 }
749 
750 static void drawCurve(wxGraphicsContext* gc, const Pixel from, const Pixel to) {
751  wxGraphicsPath path = gc->CreatePath();
752  path.MoveToPoint(from.x, from.y);
753 
754  const Pixel dp = to - from;
755  path.AddCurveToPoint(from.x + dp.x / 2, from.y, from.x + dp.x / 2, to.y, to.x, to.y);
756 
757  gc->StrokePath(path);
758 }
759 
760 static bool canConnectSlots(const NodeSlot& from, const NodeSlot& to) {
761  if (from.vis == to.vis) {
762  // connecting to the same node
764  return false;
765  }
766  if ((from.index == NodeSlot::RESULT_SLOT) == (to.index == NodeSlot::RESULT_SLOT)) {
767  // source to source or input to input
768  return false;
769  }
770 
771  if (to.index == NodeSlot::RESULT_SLOT) {
773  const SlotData fromSlot = from.vis->node->getSlot(from.index);
774  const Optional<ExtJobType> provided = to.vis->node->provides();
775  return fromSlot.used && provided && provided.value() == fromSlot.type;
776  } else {
778  const SlotData toSlot = to.vis->node->getSlot(to.index);
779  const Optional<ExtJobType> provided = from.vis->node->provides();
780  return toSlot.used && provided && provided.value() == toSlot.type;
781  }
782 }
783 
784 wxColour NodeEditor::getSlotColor(const NodeSlot& slot, const Rgba& background) {
785  if (!state.mousePosition) {
786  // paint event called before any mouse event happened, just return the default
787  return wxColour(background);
788  }
789 
790  const Pixel mousePosition = this->transform(state.mousePosition.value());
791 
792  if (state.connectingSlot == slot) {
793  // connecting source slot, always valid color
794  return wxColour(0, 220, 0);
795  } else if (getLength(slot.position() - mousePosition) >= SLOT_RADIUS) {
796  // not hovered over, background color
797  return wxColour(background);
798  } else if (state.connectingSlot && !canConnectSlots(state.connectingSlot.value(), slot)) {
799  // fail color
800  return wxColour(200, 0, 0);
801  } else {
802  // can connect or just hovering over, valid color
803  return wxColour(0, 220, 0);
804  }
805 }
806 
807 void NodeEditor::paintNode(wxGraphicsContext* gc, const Rgba& background, const VisNode& vis) {
808  const Pixel position = vis.position;
809  const Pixel size = vis.size();
810  const Optional<ExtJobType> provided = vis.node->provides();
811  // setup pen and brush
812  const bool isLightTheme = background.intensity() > 0.5f;
813  wxPen pen = getNodePen(provided.valueOr(JobType::PARTICLES), isLightTheme);
814  wxBrush brush = *wxBLACK_BRUSH;
815  Rgba brushColor;
816  if (!provided || provided.value() == JobType::PARTICLES) {
817  brushColor = decreaseContrast(background, 0.1f, isLightTheme);
818  } else {
819  brushColor = background.blend(Rgba(pen.GetColour()), 0.2f);
820  }
821  brush.SetColour(wxColour(brushColor));
822  gc->SetBrush(brush);
823  gc->SetPen(pen);
824 
825  const wxFont font = wxSystemSettings::GetFont(wxSYS_SYSTEM_FONT);
826  const Rgba lineColor(getLineColor(background));
827  gc->SetFont(font, wxColour(lineColor));
828 
829  if (&vis == state.activated) {
830  pen.SetWidth(3);
831  gc->SetPen(pen);
832  }
833 
834  // node (rounded) rectangle
835  gc->DrawRoundedRectangle(position.x, position.y, size.x, size.y, 10);
836 
837  // node instance name
838  drawCenteredText(gc, vis.node->instanceName(), position, Pixel(position.x + size.x, position.y + 23));
839 
840  // node class name
841  wxColour disabledTextColor(decreaseContrast(lineColor, 0.3f, !isLightTheme));
842  gc->SetFont(font.Smaller(), disabledTextColor);
843  drawCenteredText(gc,
844  vis.node->className(),
845  Pixel(position.x, position.y + 23),
846  Pixel(position.x + size.x, position.y + 40));
847  gc->SetFont(font, wxColour(lineColor));
848 
849  // separating line for particle nodes
850  pen = *wxBLACK_PEN;
851  if (!provided || provided.value() == JobType::PARTICLES) {
852  pen.SetColour(isLightTheme ? wxColour(160, 160, 160) : wxColour(20, 20, 20));
853  gc->SetPen(pen);
854  const int lineY = 44;
855  const int padding = (&vis == state.activated) ? 2 : 1;
856  gc->StrokeLine(
857  position.x + padding, position.y + lineY, position.x + size.x - padding, position.y + lineY);
858  pen.SetColour(isLightTheme ? wxColour(240, 240, 240) : wxColour(100, 100, 100));
859  gc->SetPen(pen);
860  gc->StrokeLine(position.x + padding,
861  position.y + lineY + 1,
862  position.x + size.x - padding,
863  position.y + lineY + 1);
864  }
865 
866  // input slots
867  for (Size i = 0; i < vis.node->getSlotCnt(); ++i) {
868  SlotData slot = vis.node->getSlot(i);
869  const Pixel p1 = NodeSlot{ &vis, i }.position();
870 
871  brush.SetColour(this->getSlotColor(NodeSlot{ &vis, i }, background));
872  gc->SetBrush(brush);
873 
874  pen = getNodePen(slot.type, isLightTheme);
875  pen.SetStyle(wxPENSTYLE_SOLID);
876  pen.SetWidth(1);
877  gc->SetPen(pen);
878  gc->DrawEllipse(p1.x - SLOT_RADIUS, p1.y - SLOT_RADIUS, 2 * SLOT_RADIUS, 2 * SLOT_RADIUS);
879 
880  if (slot.used) {
881  gc->SetFont(font, wxColour(lineColor));
882  } else {
883  gc->SetFont(font, disabledTextColor);
884  }
885  gc->DrawText(slot.name, p1.x + 14, p1.y - 10);
886  }
887 
888  // result slot
889  if (provided) {
890  const Pixel resultSlot(position.x + size.x, position.y + FIRST_SLOT_Y);
891  brush.SetColour(this->getSlotColor(NodeSlot{ &vis, NodeSlot::RESULT_SLOT }, background));
892  gc->SetBrush(brush);
893 
894  pen = getNodePen(provided.value(), isLightTheme);
895  pen.SetStyle(wxPENSTYLE_SOLID);
896  pen.SetWidth(1);
897  gc->SetPen(pen);
898  gc->DrawEllipse(
899  resultSlot.x - SLOT_RADIUS, resultSlot.y - SLOT_RADIUS, 2 * SLOT_RADIUS, 2 * SLOT_RADIUS);
900  }
901 }
902 
903 void NodeEditor::save(Config& config) {
904  SharedPtr<ConfigNode> out = config.addNode("editor_state");
905  out->set("offset", state.offset);
906  out->set("zoom", state.zoom);
907 }
908 
909 void NodeEditor::load(Config& config) {
910  SharedPtr<ConfigNode> in = config.getNode("editor_state");
911  state.offset = in->get<Pixel>("offset");
912  state.zoom = in->get<float>("zoom");
913 }
914 
915 void NodeEditor::paintCurves(wxGraphicsContext* gc, const Rgba& background, const VisNode& vis) {
916  const Pixel size = vis.size();
917 
918  wxPen pen = *wxBLACK_PEN;
919  pen.SetWidth(2);
920  pen.SetColour(getLineColor(background));
921  gc->SetPen(pen);
922 
923  if (state.mousePosition && state.connectingSlot && state.connectingSlot->vis == addressOf(vis)) {
924  const Pixel mousePosition = this->transform(state.mousePosition.value());
925  const Pixel sourcePoint = state.connectingSlot->position();
926  drawCurve(gc, sourcePoint, mousePosition);
927  }
928 
929  const NodeMap& nodes = nodeMgr->getNodes();
930  for (Size i = 0; i < vis.node->getSlotCnt(); ++i) {
931  const Pixel p1 = NodeSlot{ &vis, i }.position();
932  const SlotData slot = vis.node->getSlot(i);
933  if (slot.provider != nullptr) {
934  const Pixel childPoint = nodes[slot.provider].position;
935  const Pixel p2(childPoint.x + size.x, childPoint.y + FIRST_SLOT_Y);
936 
937  drawCurve(gc, p1, p2);
938  }
939  }
940 }
941 
942 void NodeEditor::onPaint(wxPaintEvent& UNUSED(evt)) {
944  wxPaintDC dc(this);
945 
946  wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
947  if (!gc) {
948  return;
949  }
950 
951  const wxGraphicsMatrix matrix =
952  gc->CreateMatrix(state.zoom, 0.f, 0.f, state.zoom, state.offset.x, state.offset.y);
953  gc->SetTransform(matrix);
954 
955  const NodeMap& nodes = nodeMgr->getNodes();
956 
957  const Rgba background(dc.GetBackground().GetColour());
958 
959  // first layer - curves
960  for (auto& element : nodes) {
961  this->paintCurves(gc, background, element.value);
962  }
963 
964  // second layer to paint over - nodes
965  for (auto& element : nodes) {
966  this->paintNode(gc, background, element.value);
967  }
968 
969  delete gc;
970 }
971 
972 void NodeEditor::onMouseMotion(wxMouseEvent& evt) {
973  const Pixel mousePosition(evt.GetPosition());
974  if (evt.Dragging()) {
975  if (!state.mousePosition) {
976  // unknown position, cannot compute the offset
977  state.mousePosition = mousePosition;
978  return;
979  }
980 
981  if (state.selected) {
982  // moving a node
983  state.selected->position += (mousePosition - state.mousePosition.value()) / state.zoom;
984  } else if (!state.connectingSlot) {
985  // just drag the editor
986  state.offset += mousePosition - state.mousePosition.value();
987  }
988  this->Refresh();
989  callbacks->markUnsaved(false);
990  } else {
991  const NodeSlot slot = nodeMgr->getSlotAtPosition(this->transform(mousePosition));
992  if (slot != state.lastSlot) {
993  state.lastSlot = slot;
994  this->Refresh();
995  }
996  }
997 
998  state.mousePosition = mousePosition;
999 }
1000 
1001 void NodeEditor::onMouseWheel(wxMouseEvent& evt) {
1002  constexpr float MAX_ZOOM_OUT = 0.2f;
1003  constexpr float MAX_ZOOM_IN = 4.f;
1004 
1005  const Pixel position(evt.GetPosition());
1006  const float spin = evt.GetWheelRotation();
1007  const float amount = (spin > 0.f) ? 1.2f : 1.f / 1.2f;
1008  state.zoom = clamp(state.zoom * amount, MAX_ZOOM_OUT, MAX_ZOOM_IN);
1009  if (state.zoom != MAX_ZOOM_OUT && state.zoom != MAX_ZOOM_IN) {
1010  state.offset += (position - state.offset) * (1.f - amount);
1011  }
1012  this->Refresh();
1013  callbacks->markUnsaved(false);
1014 }
1015 
1016 void NodeEditor::onLeftDown(wxMouseEvent& evt) {
1017  const Pixel mousePosition(evt.GetPosition());
1018  const Pixel position = this->transform(mousePosition);
1019 
1020  const NodeSlot slot = nodeMgr->getSlotAtPosition(position);
1021  if (slot.vis != nullptr) {
1022  state.connectingSlot = slot;
1023 
1024  if (slot.index != NodeSlot::RESULT_SLOT) {
1025  SharedPtr<JobNode> node = slot.vis->node->getSlot(slot.index).provider;
1026  if (node) {
1027  node->disconnect(slot.vis->node->sharedFromThis());
1028  }
1029  }
1030  } else {
1031  state.selected = nodeMgr->getSelectedNode(position);
1032  }
1033  state.mousePosition = mousePosition;
1034 }
1035 
1036 void NodeEditor::onLeftUp(wxMouseEvent& evt) {
1037  const Pixel mousePosition(evt.GetPosition());
1038  state.selected = nullptr;
1039 
1040  if (!state.connectingSlot) {
1041  return;
1042  }
1043  NodeSlot sourceSlot = state.connectingSlot.value();
1044 
1045  const Pixel position = this->transform(mousePosition);
1046  NodeSlot targetSlot = nodeMgr->getSlotAtPosition(position);
1047 
1048  if (targetSlot.vis != nullptr && canConnectSlots(sourceSlot, targetSlot)) {
1049  if (targetSlot.index == NodeSlot::RESULT_SLOT) {
1050  std::swap(sourceSlot, targetSlot);
1051  }
1052 
1053  RawPtr<JobNode> sourceNode = sourceSlot.vis->node;
1054  RawPtr<JobNode> targetNode = targetSlot.vis->node;
1055 
1056  // disconnect the previous one
1057  SlotData slotData = targetNode->getSlot(targetSlot.index);
1058  if (slotData.provider) {
1059  slotData.provider->disconnect(targetNode->sharedFromThis());
1060  }
1061 
1062  // connect to the new slot
1063  sourceNode->connect(targetNode->sharedFromThis(), slotData.name);
1064 
1065  callbacks->markUnsaved(true);
1066  }
1067 
1068  state.connectingSlot = NOTHING;
1069  this->Refresh();
1070 }
1071 
1072 void NodeEditor::onRightUp(wxMouseEvent& evt) {
1073  const Pixel position = (Pixel(evt.GetPosition()) - state.offset) / state.zoom;
1074 
1075  wxMenu menu;
1076  VisNode* vis = nodeMgr->getSelectedNode(position);
1077  if (vis != nullptr) {
1078  const Optional<ExtJobType> provided = vis->node->provides();
1079  if (!provided || provided.value() == JobType::PARTICLES) {
1080  menu.Append(0, "Evaluate"); // there is no visible result of other types
1081  menu.Append(1, "Render preview");
1082  }
1083  }
1084 
1085  menu.Append(2, "Evaluate all");
1086 
1087  if (vis != nullptr) {
1088  menu.Append(3, "Clone");
1089  menu.Append(4, "Clone tree");
1090  menu.Append(5, "Layout");
1091  menu.Append(6, "Delete");
1092  menu.Append(7, "Delete tree");
1093  }
1094  menu.Append(8, "Delete all");
1095 
1096  menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this, vis](wxCommandEvent& evt) {
1097  const Size index = evt.GetId();
1098  switch (index) {
1099  case 0:
1100  try {
1101  nodeMgr->startRun(*vis->node);
1102  } catch (const std::exception& e) {
1103  wxMessageBox(std::string("Cannot run the node: ") + e.what(), "Error", wxOK);
1104  }
1105  break;
1106  case 1:
1107  try {
1108  nodeWindow->createRenderPreview(*vis->node);
1109  } catch (const Exception& e) {
1110  wxMessageBox(std::string("Cannot start render preview: ") + e.what(), "Error", wxOK);
1111  }
1112  // nodeMgr->startBatch(*vis->node);
1113  break;
1114  case 2:
1115  nodeMgr->startAll();
1116  break;
1117  case 3:
1118  nodeMgr->addNode(cloneNode(*vis->node));
1119  break;
1120  case 4:
1121  nodeMgr->cloneHierarchy(*vis->node);
1122  break;
1123  case 5:
1124  nodeMgr->layoutNodes(*vis->node, vis->position);
1125  break;
1126  case 6:
1127  nodeMgr->deleteNode(*vis->node);
1128  break;
1129  case 7:
1130  nodeMgr->deleteTree(*vis->node);
1131  break;
1132  case 8:
1133  nodeMgr->deleteAll();
1134  break;
1135  default:
1137  }
1138 
1139  this->Refresh();
1140  });
1141  this->PopupMenu(&menu);
1142 }
1143 
1144 void NodeEditor::onDoubleClick(wxMouseEvent& evt) {
1145  const Pixel position(evt.GetPosition());
1146  VisNode* vis = nodeMgr->getSelectedNode((position - state.offset) / state.zoom);
1147  if (vis) {
1148  state.activated = vis;
1149  this->Refresh();
1150  nodeWindow->selectNode(*vis->node);
1151  }
1152 }
1153 
1154 
1155 //-----------------------------------------------------------------------------------------------------------
1156 // NodeWindow
1157 //-----------------------------------------------------------------------------------------------------------
1158 
1159 class DirDialogAdapter : public wxPGEditorDialogAdapter {
1160 public:
1161  virtual bool DoShowDialog(wxPropertyGrid* UNUSED(propGrid), wxPGProperty* property) override {
1162  wxDirDialog dialog(nullptr, "Choose directory", "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
1163  if (dialog.ShowModal() == wxID_OK) {
1164  wxString path = dialog.GetPath();
1165  property->SetValue(path);
1166  this->SetValue(path);
1167  return true;
1168  } else {
1169  return false;
1170  }
1171  }
1172 };
1173 
1174 class SaveFileDialogAdapter : public wxPGEditorDialogAdapter {
1175 private:
1176  Array<FileFormat> formats;
1177 
1178 public:
1180  : formats(std::move(formats)) {}
1181 
1182  virtual bool DoShowDialog(wxPropertyGrid* UNUSED(propGrid), wxPGProperty* property) override {
1183  Optional<Path> file = doSaveFileDialog("Save file...", formats.clone());
1184  if (file) {
1185  property->SetValue(file->native());
1186  this->SetValue(file->native());
1187  return true;
1188  } else {
1189  return false;
1190  }
1191  }
1192 };
1193 
1194 
1195 class OpenFileDialogAdapter : public wxPGEditorDialogAdapter {
1196 private:
1197  Array<FileFormat> formats;
1198 
1199 public:
1201  : formats(std::move(formats)) {}
1202 
1203  virtual bool DoShowDialog(wxPropertyGrid* UNUSED(propGrid), wxPGProperty* property) override {
1204  Optional<Path> file = doOpenFileDialog("Save file...", formats.clone());
1205  if (file) {
1206  property->SetValue(file->native());
1207  this->SetValue(file->native());
1208  return true;
1209  } else {
1210  return false;
1211  }
1212  }
1213 };
1214 
1215 class FileProperty : public wxFileProperty {
1216  Function<wxPGEditorDialogAdapter*()> makeAdapter;
1217 
1218 public:
1219  using wxFileProperty::wxFileProperty;
1220 
1221  void setFunc(Function<wxPGEditorDialogAdapter*()> func) {
1222  makeAdapter = func;
1223  }
1224 
1225  virtual wxPGEditorDialogAdapter* GetEditorDialog() const override {
1226  return makeAdapter();
1227  }
1228 };
1229 
1230 class VectorProperty : public wxStringProperty {
1231 private:
1232  class ComponentProperty : public wxFloatProperty {
1233  private:
1234  VectorProperty* parent;
1235 
1236  public:
1237  ComponentProperty(VectorProperty* parent, const wxString& name, const Vector& value, const Size index)
1238  : wxFloatProperty(name, wxPG_LABEL, value[index])
1239  , parent(parent) {}
1240 
1241  virtual void OnSetValue() override {
1242  wxFloatProperty::OnSetValue();
1243  parent->update();
1244  }
1245  };
1246 
1248  wxWindow* parent;
1249 
1250 public:
1251  VectorProperty(wxWindow* parent, const wxString& name, const Vector& value)
1252  : wxStringProperty(name, wxPG_LABEL, "")
1253  , parent(parent) {
1254  this->SetFlagRecursively(wxPG_PROP_READONLY, true);
1255 
1256  static StaticArray<std::string, 3> labels = { "X", "Y", "Z" };
1257  for (Size i = 0; i < 3; ++i) {
1258  components[i] = new ComponentProperty(this, labels[i], value, i);
1259  this->AppendChild(components[i]);
1260  }
1261 
1262  this->update(false);
1263  }
1264 
1265  Vector getVector() const {
1266  Vector v;
1267  for (Size i = 0; i < 3; ++i) {
1268  v[i] = components[i]->GetValue();
1269  }
1270  return v;
1271  }
1272 
1273  void update(const bool notify = true) {
1274  wxString value;
1275  for (Size i = 0; i < 3; ++i) {
1276  value += components[i]->GetValue().GetString() + ", ";
1277  }
1278  value.RemoveLast(2);
1279 
1280  this->SetValue(value);
1281 
1282  if (notify) {
1283  // SetValue does not notify the grid, so we have to do it manually
1284  wxPropertyGridEvent evt(wxEVT_PG_CHANGED);
1285  evt.SetProperty(this);
1286  parent->GetEventHandler()->ProcessEvent(evt);
1287  }
1288  }
1289 };
1290 
1291 class IntervalProperty : public wxStringProperty {
1292 private:
1293  class ComponentProperty : public wxFloatProperty {
1294  private:
1295  IntervalProperty* parent;
1296 
1297  public:
1298  ComponentProperty(IntervalProperty* parent, const wxString& name, const Float value)
1299  : wxFloatProperty(name, wxPG_LABEL, value)
1300  , parent(parent) {}
1301 
1302  virtual void OnSetValue() override {
1303  wxFloatProperty::OnSetValue();
1304  parent->update();
1305  }
1306  };
1307 
1309  wxWindow* parent;
1310 
1311 public:
1312  IntervalProperty(wxWindow* parent, const wxString& name, const Interval& value)
1313  : wxStringProperty(name, wxPG_LABEL, "")
1314  , parent(parent) {
1315  this->SetFlagRecursively(wxPG_PROP_READONLY, true);
1316 
1317  components[0] = new ComponentProperty(this, "from", value.lower());
1318  components[1] = new ComponentProperty(this, "to", value.upper());
1319  for (ComponentProperty* comp : components) {
1320  this->AppendChild(comp);
1321  }
1322 
1323  this->update(false);
1324  }
1325 
1327  return Interval(components[0]->GetValue(), components[1]->GetValue());
1328  }
1329 
1330  void update(const bool notify = true) {
1331  wxString value = "[ " + components[0]->GetValue().GetString() + ", " +
1332  components[1]->GetValue().GetString() + " ]";
1333  this->SetValue(value);
1334 
1335  if (notify) {
1336  // SetValue does not notify the grid, so we have to do it manually
1337  wxPropertyGridEvent evt(wxEVT_PG_CHANGED);
1338  evt.SetProperty(this);
1339  parent->GetEventHandler()->ProcessEvent(evt);
1340  }
1341  }
1342 };
1343 
1345 private:
1346  wxPropertyGrid* grid;
1347 
1348 public:
1349  explicit PropertyGrid(wxPropertyGrid* grid)
1350  : grid(grid) {}
1351 
1352  wxPGProperty* addCategory(const std::string& name) const {
1353  return grid->Append(new wxPropertyCategory(name));
1354  }
1355 
1356  wxPGProperty* addBool(const std::string& name, const bool value) const {
1357  return grid->Append(new wxBoolProperty(name, wxPG_LABEL, value));
1358  }
1359 
1360  wxPGProperty* addInt(const std::string& name, const int value) const {
1361  return grid->Append(new wxIntProperty(name, wxPG_LABEL, value));
1362  }
1363 
1364  wxPGProperty* addFloat(const std::string& name, const Float value) const {
1365  return grid->Append(new wxFloatProperty(name, wxPG_LABEL, value));
1366  }
1367 
1368  wxPGProperty* addVector(const std::string& name, const Vector& value) const {
1369  wxPGProperty* prop = grid->Append(new VectorProperty(grid, name, value));
1370  grid->Collapse(prop);
1371  return prop;
1372  }
1373 
1374  wxPGProperty* addInterval(const std::string& name, const Interval& value) const {
1375  wxPGProperty* prop = grid->Append(new IntervalProperty(grid, name, value));
1376  grid->Collapse(prop);
1377  return prop;
1378  }
1379 
1380  wxPGProperty* addString(const std::string& name, const std::string& value) const {
1381  return grid->Append(new wxStringProperty(name, wxPG_LABEL, value));
1382  }
1383 
1384  wxPGProperty* addPath(const std::string& name,
1385  const Path& value,
1386  const IVirtualEntry::PathType type,
1387  Array<IVirtualEntry::FileFormat>&& formats) const {
1388  FileProperty* prop = new FileProperty(name, wxPG_LABEL, value.native());
1389  if (type != IVirtualEntry::PathType::DIRECTORY) {
1390  prop->setFunc([type, formats = std::move(formats)]() -> wxPGEditorDialogAdapter* {
1392  return new OpenFileDialogAdapter(formats.clone());
1393  } else {
1394  return new SaveFileDialogAdapter(formats.clone());
1395  }
1396  });
1397  } else {
1398  prop->setFunc([] { return new DirDialogAdapter(); });
1399  }
1400  return grid->Append(prop);
1401  }
1402 
1403  wxPGProperty* addEnum(const std::string& name, const IVirtualEntry& entry) const {
1404  return addEnum<wxEnumProperty>(name, entry);
1405  }
1406 
1407  wxPGProperty* addFlags(const std::string& name, const IVirtualEntry& entry) const {
1408  return addEnum<wxFlagsProperty>(name, entry);
1409  }
1410 
1411  wxPGProperty* addExtra(const std::string& name, const ExtraEntry& extra) const {
1412  RawPtr<CurveEntry> entry = dynamicCast<CurveEntry>(extra.getEntry());
1413  SPH_ASSERT(entry);
1414  return grid->Append(new CurveProperty(name, entry->getCurve()));
1415  }
1416 
1417  void setTooltip(wxPGProperty* prop, const std::string& tooltip) const {
1418  grid->SetPropertyHelpString(prop, tooltip);
1419  }
1420 
1421 private:
1422  template <typename TProperty>
1423  wxPGProperty* addEnum(const std::string& name, const IVirtualEntry& entry) const {
1424  wxArrayString values;
1425  wxArrayInt flags;
1426  EnumWrapper wrapper = entry.get();
1427  for (int id : EnumMap::getAll(wrapper.index)) {
1428  EnumWrapper option(id, wrapper.index);
1429  if (!entry.isValid(option)) {
1430  continue;
1431  }
1432  const std::string rawName = EnumMap::toString(option.value, option.index);
1433  values.Add(capitalize(replaceAll(rawName, "_", " ")));
1434  flags.Add(option.value);
1435  }
1436  return grid->Append(new TProperty(name, wxPG_LABEL, values, flags, wrapper.value));
1437  }
1438 };
1439 
1440 
1442 private:
1443  PropertyGrid wrapper;
1444 
1445  PropertyEntryMap& propertyEntryMap;
1446 
1447 public:
1448  explicit AddParamsProc(wxPropertyGrid* grid, PropertyEntryMap& propertyEntryMapping)
1449  : wrapper(grid)
1450  , propertyEntryMap(propertyEntryMapping) {}
1451 
1452  virtual void onCategory(const std::string& name) const override {
1453  wrapper.addCategory(name);
1454  }
1455 
1456  virtual void onEntry(const std::string& UNUSED(key), IVirtualEntry& entry) const override {
1457  wxPGProperty* prop = nullptr;
1458  const std::string name = entry.getName();
1459  switch (entry.getType()) {
1461  prop = wrapper.addBool(name, entry.get());
1462  break;
1464  prop = wrapper.addInt(name, entry.get());
1465  break;
1467  prop = wrapper.addFloat(name, Float(entry.get()));
1468  break;
1470  prop = wrapper.addVector(name, entry.get());
1471  break;
1473  prop = wrapper.addInterval(name, entry.get());
1474  break;
1476  prop = wrapper.addString(name, entry.get());
1477  break;
1479  const Optional<IVirtualEntry::PathType> type = entry.getPathType();
1480  SPH_ASSERT(type, "No path type set for entry '" + entry.getName() + "'");
1482  prop = wrapper.addPath(name, entry.get(), type.value(), std::move(formats));
1483  break;
1484  }
1486  prop = wrapper.addEnum(name, entry);
1487  break;
1489  prop = wrapper.addFlags(name, entry);
1490  break;
1492  prop = wrapper.addExtra(name, entry.get());
1493  break;
1494  default:
1496  }
1497 
1498  const std::string tooltip = entry.getTooltip();
1499  if (!tooltip.empty()) {
1500  wrapper.setTooltip(prop, tooltip);
1501  }
1502 
1503  propertyEntryMap.insert(prop, &entry);
1504 
1505  SPH_ASSERT(propertyEntryMap[prop]->enabled() ||
1506  propertyEntryMap[prop]->getType() != IVirtualEntry::Type(20)); // dummy call
1507  }
1508 };
1509 
1510 
1512  : wxPanel(parent, wxID_ANY) {
1513  aui = makeAuto<wxAuiManager>(this);
1514 
1515  nodeEditor = new NodeEditor(this, callbacks);
1516  nodeMgr = makeShared<NodeManager>(nodeEditor, callbacks);
1517  nodeEditor->setNodeMgr(nodeMgr);
1518 
1519  grid = new wxPropertyGrid(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE);
1520  grid->SetExtraStyle(wxPG_EX_HELP_AS_TOOLTIPS);
1521  grid->SetMinSize(wxSize(300, -1));
1522 
1523  grid->Bind(wxEVT_PG_CHANGED, [this, callbacks](wxPropertyGridEvent& evt) {
1524  wxPGProperty* prop = evt.GetProperty();
1525  if (!propertyEntryMap.contains(prop)) {
1526  // grid being cleared or not listening to this property
1527  return;
1528  }
1529 
1530  IVirtualEntry* entry = propertyEntryMap[prop];
1531  SPH_ASSERT(entry != nullptr);
1532  wxVariant value = prop->GetValue();
1533 
1534  switch (entry->getType()) {
1535  case IVirtualEntry::Type::BOOL:
1536  entry->set(value.GetBool());
1537  break;
1538  case IVirtualEntry::Type::INT:
1539  entry->set(int(value.GetLong()));
1540  break;
1541  case IVirtualEntry::Type::FLOAT:
1542  entry->set(value.GetDouble());
1543  break;
1544  case IVirtualEntry::Type::VECTOR: {
1545  VectorProperty* vector = dynamic_cast<VectorProperty*>(prop);
1546  SPH_ASSERT(vector);
1547  entry->set(vector->getVector());
1548  break;
1549  }
1551  IntervalProperty* i = dynamic_cast<IntervalProperty*>(prop);
1552  SPH_ASSERT(i);
1553  entry->set(i->getInterval());
1554  break;
1555  }
1557  std::string stringValue(wxString(value.GetString()));
1559  if (entry->getName() == "Name") {
1560  UniqueNameManager nameMgr = nodeMgr->makeUniqueNameManager();
1561  stringValue = nameMgr.getName(stringValue);
1562  }
1563  entry->set(stringValue);
1564  break;
1565  }
1567  entry->set(Path(std::string(value.GetString())));
1568  break;
1571  EnumWrapper ew = entry->get();
1572  ew.value = value.GetLong();
1573  entry->set(ew);
1574  break;
1575  }
1577  CurveProperty* curveProp = dynamic_cast<CurveProperty*>(prop);
1578  SPH_ASSERT(curveProp != nullptr);
1579  Curve curve = curveProp->getCurve();
1580  ExtraEntry extra(makeAuto<CurveEntry>(curve));
1581  entry->set(extra);
1582  break;
1583  }
1584  default:
1586  }
1587 
1588  if (entry->hasSideEffect()) {
1589  // need to update all properties
1591  this->updateProperties();
1592  } else {
1593  // only update the enabled/disabled state
1594  this->updateEnabled(grid);
1595  }
1596  nodeEditor->Refresh();
1597  callbacks->markUnsaved(true);
1598  });
1599 
1600 
1601  wxTreeCtrl* jobView =
1602  new wxTreeCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT);
1603  jobView->SetMinSize(wxSize(300, -1));
1604 
1605  wxTreeItemId rootId = jobView->AddRoot("Nodes");
1606 
1607  class JobTreeData : public wxTreeItemData {
1608  RawPtr<IJobDesc> desc;
1609 
1610  public:
1611  explicit JobTreeData(RawPtr<IJobDesc> desc)
1612  : desc(desc) {}
1613 
1614  AutoPtr<IJob> create() const {
1615  return desc->create(NOTHING);
1616  }
1617 
1618  std::string tooltip() const {
1619  return desc->tooltip();
1620  }
1621  };
1622 
1623  FlatMap<std::string, wxTreeItemId> categoryItemIdMap;
1624  for (const AutoPtr<IJobDesc>& desc : enumerateRegisteredJobs()) {
1625  const std::string& cat = desc->category();
1626  if (Optional<wxTreeItemId&> id = categoryItemIdMap.tryGet(cat)) {
1627  jobView->AppendItem(id.value(), desc->className(), -1, -1, new JobTreeData(desc.get()));
1628  } else {
1629  wxTreeItemId catId = jobView->AppendItem(rootId, cat);
1630  jobView->AppendItem(catId, desc->className(), -1, -1, new JobTreeData(desc.get()));
1631  categoryItemIdMap.insert(cat, catId);
1632  }
1633  }
1634 
1635  wxTreeItemId presetsId = jobView->AppendItem(rootId, "presets");
1636  std::map<wxTreeItemId, Presets::Id> presetsIdMap;
1637  for (Presets::Id id : EnumMap::getAll<Presets::Id>()) {
1638  std::string name = replaceAll(EnumMap::toString(id), "_", " ");
1639  wxTreeItemId itemId = jobView->AppendItem(presetsId, name);
1640  presetsIdMap[itemId] = id;
1641  }
1642 
1643  jobView->Bind(wxEVT_MOTION, [this, jobView](wxMouseEvent& evt) {
1644  wxPoint pos = evt.GetPosition();
1645  int flags;
1646  wxTreeItemId id = jobView->HitTest(pos, flags);
1647 
1648  static DelayedCallback callback;
1649  if (flags & wxTREE_HITTEST_ONITEMLABEL) {
1650  JobTreeData* data = dynamic_cast<JobTreeData*>(jobView->GetItemData(id));
1651  if (data) {
1652  callback.start(600, [this, jobView, id, data, pos] {
1653  const wxString name = jobView->GetItemText(id);
1654  wxRichToolTip tip(name, setLineBreak(data->tooltip(), 50));
1655  const wxRect rect(pos, pos);
1656  tip.ShowFor(jobView, &rect);
1657  tip.SetTimeout(1e6);
1658 
1659  nodeEditor->invalidateMousePosition();
1660  });
1661  }
1662  } else {
1663  callback.stop();
1664  }
1665  });
1666 
1667  jobView->Bind(wxEVT_TREE_ITEM_ACTIVATED, [=](wxTreeEvent& evt) {
1668  wxTreeItemId id = evt.GetItem();
1669  UniqueNameManager nameMgr = nodeMgr->makeUniqueNameManager();
1670  if (presetsIdMap.find(id) != presetsIdMap.end()) {
1671  SharedPtr<JobNode> presetNode = Presets::make(presetsIdMap.at(id), nameMgr);
1672  nodeMgr->addNodes(*presetNode);
1673  }
1674 
1675  JobTreeData* data = dynamic_cast<JobTreeData*>(jobView->GetItemData(id));
1676  if (data) {
1677  AutoPtr<IJob> job = data->create();
1678  if (RawPtr<LoadFileJob> loader = dynamicCast<LoadFileJob>(job.get())) {
1679  Optional<Path> path = doOpenFileDialog("Load file", getInputFormats());
1680  if (path) {
1681  VirtualSettings settings = loader->getSettings();
1682  settings.set("file", path.value());
1683  // settings.set("name", "Load '" + path->fileName().native() + "'");
1684  }
1685  }
1686  if (RawPtr<SaveFileJob> saver = dynamicCast<SaveFileJob>(job.get())) {
1687  Optional<Path> path = doSaveFileDialog("Save file", getOutputFormats());
1688  if (path) {
1689  VirtualSettings settings = saver->getSettings();
1690  settings.set(RunSettingsId::RUN_OUTPUT_NAME, path.value());
1691  const Optional<IoEnum> type = getIoEnum(path->extension().native());
1692  if (type) {
1694  } else {
1695  wxMessageBox(
1696  "Unknown file extension '" + path->extension().native() + "'", "Error", wxOK);
1697  return;
1698  }
1699  }
1700  }
1701  SharedPtr<JobNode> node = makeShared<JobNode>(std::move(job));
1702  VisNode* vis = nodeMgr->addNode(node);
1703  nodeEditor->activate(vis);
1704  this->selectNode(*node);
1705  callbacks->markUnsaved(true);
1706  }
1707  });
1708 
1709  /*wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
1710  sizer->Add(grid, 1, wxEXPAND | wxLEFT);
1711  sizer->Add(mainPanel, 3, wxEXPAND | wxALL);
1712  sizer->Add(jobView, 1, wxEXPAND | wxRIGHT);
1713  this->SetSizerAndFit(sizer);*/
1714  this->SetAutoLayout(true);
1715 
1716  wxAuiPaneInfo info;
1717  info.Left().MinSize(wxSize(300, -1));
1718  aui->AddPane(grid, info);
1719  aui->AddPane(nodeEditor, wxCENTER);
1720 
1721  info.Right();
1722  aui->AddPane(jobView, info);
1723  aui->Update();
1724 
1725  panelInfoMap.insert(ID_LIST, &aui->GetPane(jobView));
1726  panelInfoMap.insert(ID_PROPERTIES, &aui->GetPane(grid));
1727  /*this->Bind(wxEVT_SIZE, [this](wxSizeEvent& UNUSED(evt)) {
1728  // this->Fit();
1729  this->Layout();
1730  });*/
1731 }
1732 
1734  aui->UnInit();
1735  aui = nullptr;
1736 }
1737 
1739  panelInfoMap[id]->Show();
1740  aui->Update();
1741 }
1742 
1743 void NodeWindow::selectNode(const JobNode& node) {
1744  grid->CommitChangesFromEditor();
1745 
1746  settings = node.getSettings();
1747  this->updateProperties();
1748 }
1749 
1751  grid->CommitChangesFromEditor();
1752  grid->Clear();
1753  propertyEntryMap.clear();
1754 }
1755 
1757  settings = nodeMgr->getGlobalSettings();
1758  this->updateProperties();
1759 }
1760 
1762  nodeMgr->showBatchDialog();
1763 }
1764 
1766  nodeMgr->selectRun();
1767 }
1768 
1769 void NodeWindow::startScript(const Path& file) {
1770  nodeMgr->startScript(file);
1771 }
1772 
1774  nodeMgr->deleteAll();
1775  this->clearGrid();
1776 }
1777 
1778 void NodeWindow::save(Config& config) {
1779  grid->CommitChangesFromEditor();
1780 
1781  nodeMgr->save(config);
1782  nodeEditor->save(config);
1783 }
1784 
1785 void NodeWindow::load(Config& config) {
1786  nodeMgr->load(config);
1787  nodeEditor->load(config);
1788 }
1789 
1791  nodeMgr->addNode(node);
1792 }
1793 
1795  nodeMgr->addNodes(node);
1796 }
1797 
1798 
1800  SharedPtr<JobNode> node = makeShared<JobNode>(std::move(job));
1801  nodeMgr->addNode(node);
1802  return node;
1803 }
1804 
1806  renderPane = nodeMgr->createRenderPreview(this, node);
1807  wxAuiPaneInfo info;
1808  info.Right()
1809  .MinSize(wxSize(300, 300))
1810  .CaptionVisible(true)
1811  .DockFixed(false)
1812  .CloseButton(true)
1813  .Caption("Preview")
1814  //.Window(renderPane)
1815  .DestroyOnClose();
1816  aui->AddPane(renderPane, info);
1817  aui->Update();
1818 
1819  // forces re-generation of settings after installing the accessors
1820  this->selectNode(node);
1821 }
1822 
1823 void NodeWindow::updateProperties() {
1824  const wxString states = grid->SaveEditableState(wxPropertyGrid::ScrollPosState);
1825  grid->Clear();
1826  propertyEntryMap.clear();
1827 
1828  try {
1829  AddParamsProc proc(grid, propertyEntryMap);
1830  settings.enumerate(proc);
1831  } catch (const Exception& e) {
1832  SPH_ASSERT(false, e.what());
1833  }
1834  this->updateEnabled(grid);
1835 
1836  grid->RestoreEditableState(states, wxPropertyGrid::ScrollPosState);
1837 }
1838 
1839 void NodeWindow::updateEnabled(wxPropertyGrid* grid) {
1840  for (wxPropertyGridIterator iter = grid->GetIterator(); !iter.AtEnd(); iter.Next()) {
1841  wxPGProperty* prop = iter.GetProperty();
1842  if (!propertyEntryMap.contains(prop)) {
1843  continue;
1844  }
1845 
1846  IVirtualEntry* entry = propertyEntryMap[prop];
1847  SPH_ASSERT(entry != nullptr);
1848  const bool enabled = entry->enabled();
1849  prop->Enable(enabled);
1850  }
1851 }
1852 
1854  return nodeMgr->makeUniqueNameManager();
1855 }
1856 
#define SPH_ASSERT(x,...)
Definition: Assert.h:94
#define NOT_IMPLEMENTED
Helper macro marking missing implementation.
Definition: Assert.h:100
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
Helper functions to check the internal consistency of the code.
@ NO_THROW
Function cannot throw exceptions.
@ MAIN_THREAD
Function can only be executed from main thread.
#define CHECK_FUNCTION(flags)
Definition: CheckFunction.h:40
Interface for the configuration files storing job data.
uint32_t Size
Integral type used to index arrays (by default).
Definition: Globals.h:16
double Float
Precision used withing the code. Use Float instead of float or double where precision is important.
Definition: Globals.h:13
Helper objects allowing to iterate in reverse, iterate over multiple containeres, etc.
ReverseAdapter< TContainer > reverse(TContainer &&container)
ArrayView< const AutoPtr< IJobDesc > > enumerateRegisteredJobs()
Returns a view of all currently registered jobs.
Definition: Job.cpp:30
RawPtr< IJobDesc > getJobDesc(const std::string &name)
Returns a job descriptor for given class name.
Definition: Job.cpp:34
@ GEOMETRY
Job providing geometry.
@ MATERIAL
Job providing a material.
@ PARTICLES
Job providing particles.
constexpr INLINE T clamp(const T &f, const T &f1, const T &f2)
Definition: MathBasic.h:35
INLINE Float root(const Float f)
constexpr NAMESPACE_SPH_BEGIN int FIRST_SLOT_Y
Definition: NodePage.cpp:39
constexpr int SLOT_RADIUS
Definition: NodePage.cpp:41
constexpr int SLOT_DY
Definition: NodePage.cpp:40
SharedPtr< JobNode > cloneHierarchy(JobNode &node, const Optional< std::string > &prefix)
Clones all nodes in the hierarchy.
Definition: Node.cpp:282
AutoPtr< JobNode > cloneNode(const JobNode &node, const std::string &name)
Clones a single node.
Definition: Node.cpp:269
#define UNUSED(x)
Definition: Object.h:37
#define NAMESPACE_SPH_END
Definition: Object.h:12
const NothingType NOTHING
Definition: Optional.h:16
INLINE RawPtr< T > addressOf(T &ref)
Definition: RawPtr.h:82
Additional bindings to IVirtualSettings.
std::string setLineBreak(const std::string &s, const Size lineWidth)
Inserts to string so that no line is longer than given limit.
std::string replaceAll(const std::string &source, const std::string &old, const std::string &s)
Replaces all occurences of string with a new string.
std::string capitalize(const std::string &input)
Capitalizes first letters of all words in the string, except for words like 'and',...
INLINE auto makeTuple(TArgs &&... args)
Creates a tuple from a pack of values, utilizing type deduction.
Definition: Tuple.h:298
Optional< Path > doSaveFileDialog(const std::string &title, Array< FileFormat > &&formats)
Definition: Utils.cpp:56
Optional< Path > doOpenFileDialog(const std::string &title, Array< FileFormat > &&formats)
Definition: Utils.cpp:45
Random utility functions for drawing stuff to DC.
INLINE Float getLength(const Vector &v)
Returns the length of the vector. Enabled only for vectors of floating-point precision.
Definition: Vector.h:579
Array< IVirtualEntry::FileFormat > getInputFormats()
Convenience function, returning the list of input file formats defined by IoEnum.
Array< IVirtualEntry::FileFormat > getOutputFormats()
Convenience function, returning the list of output file formats defined by IoEnum.
virtual void onEntry(const std::string &UNUSED(key), IVirtualEntry &entry) const override
Definition: NodePage.cpp:1456
virtual void onCategory(const std::string &name) const override
Called for every category in the settings.
Definition: NodePage.cpp:1452
AddParamsProc(wxPropertyGrid *grid, PropertyEntryMap &propertyEntryMapping)
Definition: NodePage.cpp:1448
Generic dynamically allocated resizable storage.
Definition: Array.h:43
INLINE void push(U &&u)
Adds new element to the end of the array, resizing the array if necessary.
Definition: Array.h:306
INLINE TCounter size() const noexcept
Definition: Array.h:193
INLINE T & front() noexcept
Definition: Array.h:166
INLINE bool empty() const noexcept
Definition: Array.h:201
Array clone() const
Performs a deep copy of all elements of the array.
Definition: Array.h:147
INLINE RawPtr< T > get() const
Definition: AutoPtr.h:69
BatchManager & getBatch()
Definition: BatchDialog.h:144
virtual UnorderedMap< std::string, ExtJobType > getSlots() const override
Lists all potential inputs of the job.
Definition: NodePage.cpp:460
BatchJob(const std::string &name, const Size runCnt)
Definition: NodePage.cpp:452
virtual VirtualSettings getSettings() override
Returns a settings object which allows to query and modify the state of the job.
Definition: NodePage.cpp:468
virtual std::string className() const override
Name representing the type of the job (e.e. "SPH").
Definition: NodePage.cpp:456
virtual void evaluate(const RunSettings &UNUSED(global), IRunCallbacks &UNUSED(callbacks)) override
Definition: NodePage.cpp:472
void save(Config &config)
Size getParamCount() const
Definition: BatchDialog.h:49
void load(Config &config, ArrayView< const SharedPtr< JobNode >> nodes)
Definition: BatchDialog.cpp:94
std::string getRunName(const Size rowIdx) const
Definition: BatchDialog.h:41
Size getRunCount() const
Definition: BatchDialog.h:37
void modifyHierarchy(const Size runIdx, JobNode &node)
Modifies the settings of the given node hierarchy.
Definition: BatchDialog.cpp:14
BatchManager clone() const
Definition: BatchDialog.h:29
SharedPtr< JobNode > getParamNode(const Size colIdx) const
Definition: BatchDialog.h:57
Represents a single node in the hierarchy written into config file.
Definition: Config.h:198
Optional< Type > tryGet(const std::string &name)
Tries to return a value stored in the node.
Definition: Config.h:232
Type get(const std::string &name)
Returns a value stored in the node.
Definition: Config.h:220
void set(const std::string &name, const Type &value)
Adds a new value into the node.
Definition: Config.h:211
Provides functionality for reading and writing configuration files.
Definition: Config.h:272
SharedPtr< ConfigNode > addNode(const std::string &name)
Adds a new node to the config.
Definition: Config.cpp:81
SharedPtr< ConfigNode > getNode(const std::string &name)
Returns a node with given name.
Definition: Config.cpp:85
void start(const int milliseconds, Function< void()> newCallback)
virtual bool DoShowDialog(wxPropertyGrid *UNUSED(propGrid), wxPGProperty *property) override
Definition: NodePage.cpp:1161
static Array< TEnum > getAll()
Definition: EnumMap.h:140
static std::string toString(const TEnum value)
Definition: EnumMap.h:67
Generic exception.
Definition: Exceptions.h:10
virtual const char * what() const noexcept
Definition: Exceptions.h:18
Copyable wrapper of a IExtraEntry.
void fromString(const std::string &s) const
std::string toString() const
RawPtr< IExtraEntry > getEntry() const
void setFunc(Function< wxPGEditorDialogAdapter *()> func)
Definition: NodePage.cpp:1221
virtual wxPGEditorDialogAdapter * GetEditorDialog() const override
Definition: NodePage.cpp:1225
Container of key-value pairs.
Definition: FlatMap.h:19
INLINE bool contains(const TKey &key) const
Returns true if the map contains element of given key.
Definition: FlatMap.h:140
INLINE TValue & insert(const TKey &key, const TValue &value)
Adds a new element into the map or sets new value of element with the same key.
Definition: FlatMap.h:65
INLINE void clear()
Removes all elements from the map.
Definition: FlatMap.h:111
INLINE Optional< TValue & > tryGet(const TKey &key)
Returns a reference to the value matching the given key, or NOTHING if no such value exists.
Definition: FlatMap.h:118
virtual void startRun(SharedPtr< INode > node, const RunSettings &settings, const std::string &name) const =0
virtual void markUnsaved(bool addToUndo) const =0
Base class for all jobs providing particle data.
Definition: Job.h:242
Callbacks executed by the simulation to provide feedback to the user.
Definition: IRun.h:27
Represents a virtual entry in the settings.
virtual bool set(const Value &value)=0
Modifies the current value of the entry.
virtual Array< FileFormat > getFileFormats() const
Returns the allowed file format for this file entry.
virtual bool isValid(const Value &value) const =0
Checks if the given value is a valid input for this entry.
virtual std::string getTooltip() const
Returns an optional description of the entry.
virtual Optional< PathType > getPathType() const
Returns the type of the path.
virtual std::string getName() const =0
Returns a descriptive name of the entry.
virtual bool hasSideEffect() const
Returns true if the entry can modify multiple values simultaneously.
virtual Value get() const =0
Returns the currently stored value.
virtual Type getType() const =0
Returns the type of this entry.
virtual bool enabled() const =0
Returns if this entry is currently enabled.
void update(const bool notify=true)
Definition: NodePage.cpp:1330
IntervalProperty(wxWindow *parent, const wxString &name, const Interval &value)
Definition: NodePage.cpp:1312
Interval getInterval() const
Definition: NodePage.cpp:1326
Object representing a 1D interval of real numbers.
Definition: Interval.h:17
INLINE Float lower() const
Returns lower bound of the interval.
Definition: Interval.h:74
INLINE Float upper() const
Returns upper bound of the interval.
Definition: Interval.h:79
Thrown when components of the run are mutually incompatible.
Definition: Exceptions.h:24
Building block of a simulation hierarchy.
Definition: Node.h:88
Size getSlotCnt() const
Returns the number of provider slots of this node.
Definition: Node.cpp:132
Size getDependentCnt() const
Returns the number of dependent nodes.
Definition: Node.cpp:154
std::string instanceName() const
Returns the instance name of the job.
Definition: Node.cpp:14
std::string className() const
Returns the class name of the job.
Definition: Node.cpp:10
Optional< ExtJobType > provides() const
Returns the type of the job.
Definition: Node.cpp:56
SlotData getSlot(const Size index) const
Returns the information about given slot.
Definition: Node.cpp:136
void disconnect(SharedPtr< JobNode > dependent)
Disconnects this node from given dependent node.
Definition: Node.cpp:90
void disconnectAll()
Disconnects all dependent nodes from this node.
Definition: Node.cpp:126
void connect(SharedPtr< JobNode > dependent, const std::string &slotName)
Connects this node to given dependent node.
Definition: Node.cpp:60
void enumerate(Function< void(const SharedPtr< JobNode > &job)> func)
Enumerates all nodes in the hierarchy.
VirtualSettings getSettings() const
Returns settings object allowing to access and modify the state of the job.
Definition: Node.cpp:39
virtual void onCategory(const std::string &UNUSED(name)) const override
Definition: NodePage.cpp:330
virtual void onEntry(const std::string &name, IVirtualEntry &entry) const override
Called for every entry in the current category.
Definition: NodePage.cpp:332
LoadProc(ConfigNode &input)
Definition: NodePage.cpp:327
void setNodeMgr(SharedPtr< NodeManager > mgr)
Definition: NodePage.h:168
Pixel transform(const Pixel position) const
Definition: NodePage.h:176
NodeEditor(NodeWindow *parent, SharedPtr< INodeManagerCallbacks > callbacks)
Definition: NodePage.cpp:652
void save(Config &config)
Definition: NodePage.cpp:903
Pixel offset
Translation of the panel.
Definition: NodePage.h:145
void load(Config &config)
Definition: NodePage.cpp:909
Optional< Pixel > mousePosition
Definition: NodePage.h:150
const NodeMap & getNodes() const
Definition: NodePage.h:94
Array< SharedPtr< JobNode > > getRootNodes() const
Definition: NodePage.cpp:547
void startRun(JobNode &node)
Definition: NodePage.cpp:441
void showBatchDialog()
Definition: NodePage.cpp:597
void deleteAll()
Definition: NodePage.cpp:194
void load(Config &config)
Definition: NodePage.cpp:383
void startBatch(JobNode &node)
Definition: NodePage.cpp:477
UniqueNameManager makeUniqueNameManager() const
Definition: NodePage.cpp:587
VirtualSettings getGlobalSettings()
Definition: NodePage.cpp:559
void addNodes(JobNode &node)
Definition: NodePage.cpp:92
VisNode * getSelectedNode(const Pixel position)
Definition: NodePage.cpp:200
void deleteNode(JobNode &node)
Definition: NodePage.cpp:174
void startScript(const Path &file)
Definition: NodePage.cpp:509
void startAll()
Definition: NodePage.cpp:525
void layoutNodes(JobNode &node, const Pixel position)
Definition: NodePage.cpp:124
NodeManager(NodeEditor *editor, SharedPtr< INodeManagerCallbacks > callbacks)
Definition: NodePage.cpp:55
void selectRun()
Definition: NodePage.cpp:614
RenderPane * createRenderPreview(wxWindow *parent, JobNode &node)
Definition: NodePage.cpp:610
NodeSlot getSlotAtPosition(const Pixel position)
Definition: NodePage.cpp:213
void cloneHierarchy(JobNode &node)
Definition: NodePage.cpp:100
VisNode * addNode(const SharedPtr< JobNode > &node)
Definition: NodePage.cpp:87
void save(Config &config)
Definition: NodePage.cpp:285
void deleteTree(JobNode &node)
Definition: NodePage.cpp:185
void selectNode(const JobNode &node)
Definition: NodePage.cpp:1743
SharedPtr< JobNode > createNode(AutoPtr< IJob > &&job)
Definition: NodePage.cpp:1799
void clearGrid()
Definition: NodePage.cpp:1750
void addNode(const SharedPtr< JobNode > &node)
Definition: NodePage.cpp:1790
void load(Config &config)
Definition: NodePage.cpp:1785
UniqueNameManager makeUniqueNameManager() const
Definition: NodePage.cpp:1853
void addNodes(JobNode &node)
Definition: NodePage.cpp:1794
void showPanel(const PanelId id)
Definition: NodePage.cpp:1738
void save(Config &config)
Definition: NodePage.cpp:1778
void selectRun()
Definition: NodePage.cpp:1765
NodeWindow(wxWindow *parent, SharedPtr< INodeManagerCallbacks > callbacks)
Definition: NodePage.cpp:1511
void createRenderPreview(JobNode &node)
Definition: NodePage.cpp:1805
void reset()
Definition: NodePage.cpp:1773
void startScript(const Path &file)
Definition: NodePage.cpp:1769
void showBatchDialog()
Definition: NodePage.cpp:1761
void showGlobals()
Definition: NodePage.cpp:1756
OpenFileDialogAdapter(Array< FileFormat > &&formats)
Definition: NodePage.cpp:1200
virtual bool DoShowDialog(wxPropertyGrid *UNUSED(propGrid), wxPGProperty *property) override
Definition: NodePage.cpp:1203
INLINE Type & value()
Returns the reference to the stored value.
Definition: Optional.h:172
INLINE Type valueOr(const TOther &other) const
Returns the stored value if the object has been initialized, otherwise returns provided parameter.
Definition: Optional.h:188
Object representing a path on a filesystem.
Definition: Path.h:17
std::string native() const
Returns the native version of the path.
Definition: Path.cpp:71
wxPGProperty * addInterval(const std::string &name, const Interval &value) const
Definition: NodePage.cpp:1374
wxPGProperty * addInt(const std::string &name, const int value) const
Definition: NodePage.cpp:1360
PropertyGrid(wxPropertyGrid *grid)
Definition: NodePage.cpp:1349
wxPGProperty * addString(const std::string &name, const std::string &value) const
Definition: NodePage.cpp:1380
wxPGProperty * addFlags(const std::string &name, const IVirtualEntry &entry) const
Definition: NodePage.cpp:1407
wxPGProperty * addEnum(const std::string &name, const IVirtualEntry &entry) const
Definition: NodePage.cpp:1403
void setTooltip(wxPGProperty *prop, const std::string &tooltip) const
Definition: NodePage.cpp:1417
wxPGProperty * addExtra(const std::string &name, const ExtraEntry &extra) const
Definition: NodePage.cpp:1411
wxPGProperty * addFloat(const std::string &name, const Float value) const
Definition: NodePage.cpp:1364
wxPGProperty * addPath(const std::string &name, const Path &value, const IVirtualEntry::PathType type, Array< IVirtualEntry::FileFormat > &&formats) const
Definition: NodePage.cpp:1384
wxPGProperty * addCategory(const std::string &name) const
Definition: NodePage.cpp:1352
wxPGProperty * addBool(const std::string &name, const bool value) const
Definition: NodePage.cpp:1356
wxPGProperty * addVector(const std::string &name, const Vector &value) const
Definition: NodePage.cpp:1368
Non-owning wrapper of pointer.
Definition: RawPtr.h:19
INLINE T * get() const
Definition: RawPtr.h:67
Definition: Color.h:8
Rgba brighten(const float amount) const
Returns a color brighter by given factor.
Definition: Color.h:145
float intensity() const
Returns the average intensity of the color.
Definition: Color.h:115
Rgba darken(const float amount) const
Returns a color darker by given factor.
Definition: Color.h:137
Rgba blend(const Rgba &other, const float amount) const
Computes a linear interpolation of two color.
Definition: Color.h:158
SharedPtr< JobNode > selectedNode() const
bool remember() const
SaveFileDialogAdapter(Array< FileFormat > &&formats)
Definition: NodePage.cpp:1179
virtual bool DoShowDialog(wxPropertyGrid *UNUSED(propGrid), wxPGProperty *property) override
Definition: NodePage.cpp:1182
SaveProc(ConfigNode &out)
Definition: NodePage.cpp:237
virtual void onCategory(const std::string &UNUSED(name)) const override
Definition: NodePage.cpp:240
virtual void onEntry(const std::string &name, IVirtualEntry &entry) const override
Called for every entry in the current category.
Definition: NodePage.cpp:244
Settings & set(const TEnum idx, TValue &&value, std::enable_if_t<!std::is_enum< std::decay_t< TValue >>::value, int >=0)
Saves a value into the settings.
Definition: Settings.h:226
SharedPtr< T > sharedFromThis() const
Definition: SharedPtr.h:426
INLINE RawPtr< T > get() const
Definition: SharedPtr.h:223
std::string getName(const std::string &name)
INLINE void clear()
Removes all elements from the map.
Definition: UnorderedMap.h:97
INLINE TValue & insert(const TKey &key, const TValue &value)
Adds a new element into the map or sets new value of element with the same key.
Definition: UnorderedMap.h:51
INLINE void remove(const TKey &key)
Removes element with given key from the map.
Definition: UnorderedMap.h:75
Vector getVector() const
Definition: NodePage.cpp:1265
VectorProperty(wxWindow *parent, const wxString &name, const Vector &value)
Definition: NodePage.cpp:1251
void update(const bool notify=true)
Definition: NodePage.cpp:1273
EntryControl & connect(const std::string &name, const std::string &key, TValue &value)
Connects to given reference.
Interface allowing to enumerate all entries in the settings.
Holds a map of virtual entries, associated with a unique name.
void enumerate(const IEntryProc &proc)
Enumerates all entries stored in the settings.
void set(const std::string &key, const IVirtualEntry::Value &value)
Modifies an existing entry in the settings.
Category & addCategory(const std::string &name)
Creates a new category of entries.
SharedPtr< T > lock() const
Definition: SharedPtr.h:373
Optional< IoEnum > getIoEnum(const std::string &ext)
Returns the file type from file extension.
Definition: Settings.cpp:367
@ UNIFORM
Mersenne Twister PRNG from Standard library.
@ SPHERICAL
Spherical mapping.
@ CUBIC_SPLINE
M4 B-spline (piecewise cubic polynomial)
@ RUN_AUTHOR
Name of the person running the simulation.
@ RUN_EMAIL
E-mail of the person running the simulation.
@ FINDER_LEAF_SIZE
Maximum number of particles in a leaf node.
@ SPH_KERNEL
Index of SPH Kernel, see KernelEnum.
@ GENERATE_UVWS
If true, the mapping coordinates will be computed and saved for all bodies in the simulation.
@ RUN_THREAD_CNT
Number of threads used by the code. If 0, all available threads are used.
@ RUN_COMMENT
User-specified comment.
@ RUN_OUTPUT_TYPE
Selected format of the output file, see IoEnum.
@ UVW_MAPPING
Type of the UV mapping.
@ RUN_RNG
Selected random-number generator used within the run.
@ RUN_RNG_SEED
Seed for the random-number generator.
@ FINDER_MAX_PARALLEL_DEPTH
@ RUN_OUTPUT_NAME
File name of the output file (including extension), where d is a placeholder for output number.
Vector position(const Float a, const Float e, const Float u)
Computes the position on the elliptic trajectory.
Definition: TwoBody.cpp:82
SharedPtr< JobNode > make(const Id id, UniqueNameManager &nameMgr, const Size particleCnt=10000)
Creates a node tree for the preset with given ID.
Definition: Presets.cpp:39
Overload of std::swap for Sph::Array.
Definition: Array.h:578
void swap(Sph::Array< T, TCounter > &ar1, Sph::Array< T, TCounter > &ar2)
Definition: Array.h:580
Wrapper of an enum.
Definition: Settings.h:37
int value
Definition: Settings.h:38
EnumIndex index
Definition: Settings.h:40
Size index
Definition: NodePage.h:42
Pixel position() const
Definition: NodePage.cpp:677
RawPtr< const VisNode > vis
Definition: NodePage.h:41
static constexpr Size RESULT_SLOT
Definition: NodePage.h:44
Definition: Point.h:101
Definition: Node.h:58
bool used
Whether the node is used by the job.
Definition: Node.h:69
ExtJobType type
Specifies the type of the slot, or the type of the node connecting to it.
Definition: Node.h:63
std::string name
Identifier of the slot, used by the job to obtain the provided data.
Definition: Node.h:60
SharedPtr< JobNode > provider
Node currently connected to the slot.
Definition: Node.h:74
RawPtr< JobNode > node
Definition: NodePage.h:24
Pixel size() const
Definition: NodePage.h:35
static constexpr Size SIZE_X
Definition: NodePage.h:33
Pixel position
Definition: NodePage.h:25