Vuo  2.4.0
VuoRendererComposition.cc
Go to the documentation of this file.
1
10#include "VuoFileType.h"
11
13
14#include "VuoCompiler.hh"
15#include "VuoCompilerCable.hh"
17#include "VuoCompilerDriver.hh"
22#include "VuoCompilerIssue.hh"
24#include "VuoCompilerType.hh"
25#include "VuoComment.hh"
26#include "VuoComposition.hh"
28#include "VuoNodeClass.hh"
29#include "VuoRendererComment.hh"
35#include "VuoRendererFonts.hh"
37
38#include "VuoStringUtilities.hh"
39
40#include "VuoHeap.h"
41
42#include "VuoTextHtml.h"
43#include "VuoUrl.h"
44
45#ifdef __APPLE__
46#include <objc/message.h>
47#endif
48
49int VuoRendererComposition::gridOpacity = 0;
50VuoRendererComposition::GridType VuoRendererComposition::gridType = VuoRendererComposition::NoGrid;
51const int VuoRendererComposition::minorGridLineSpacing = 15; // VuoRendererPort::portSpacing
53const string VuoRendererComposition::deprecatedDefaultDescription = "This composition does...";
54
65VuoRendererComposition::VuoRendererComposition(VuoComposition *baseComposition, bool renderMissingAsPresent, bool enableCaching)
66 : VuoBaseDetail<VuoComposition>("VuoRendererComposition", baseComposition)
67{
68 getBase()->setRenderer(this);
69
70 VuoRendererFonts::getSharedFonts(); // Load the fonts now to avoid a delay later when rendering the first item in the composition.
71
73 this->renderMissingAsPresent = renderMissingAsPresent;
74 this->renderNodeActivity = false;
75 this->renderPortActivity = false;
76 this->renderHiddenCables = false;
77 this->cachingEnabled = enableCaching;
78 this->publishedInputNode = createPublishedInputNode();
79 this->publishedOutputNode = createPublishedOutputNode();
80
81 parser = NULL;
82
84
86}
87
92{
93 set<VuoNode *> nodes = getBase()->getNodes();
94 foreach (VuoNode *node, nodes)
95 addNodeInCompositionToCanvas(node);
96
97 vector<VuoPublishedPort *> publishedInputPorts = getBase()->getPublishedInputPorts();
98 foreach (VuoPublishedPort *publishedPort, publishedInputPorts)
100
101 vector<VuoPublishedPort *> publishedOutputPorts = getBase()->getPublishedOutputPorts();
102 foreach (VuoPublishedPort *publishedPort, publishedOutputPorts)
104
105 set<VuoCable *> cables = getBase()->getCables();
106 foreach (VuoCable *cable, cables)
107 {
108 addCableInCompositionToCanvas(cable);
109
110 if (cable->isPublishedInputCable())
111 cable->setFrom(publishedInputNode, cable->getFromPort());
112 if (cable->isPublishedOutputCable())
113 cable->setTo(publishedOutputNode, cable->getToPort());
114 }
115
116 foreach (VuoComment *comment, getBase()->getComments())
117 addCommentInCompositionToCanvas(comment);
118
120
121 // Now that all renderer components have been created, calculate
122 // the final positions of collapsed "Make List" drawers.
123 foreach (VuoNode *node, nodes)
125}
126
131{
132 if (transparent)
133 setBackgroundBrush(Qt::transparent);
134 else
135 setBackgroundBrush(VuoRendererColors::getSharedColors()->canvasFill());
136}
137
142{
143 VuoRendererNode *rn = NULL;
144
146 rn = new VuoRendererReadOnlyDictionary(baseNode, signaler);
147
150
153
155 rn = new VuoRendererInputListDrawer(baseNode, signaler);
156
157 else
158 rn = new VuoRendererNode(baseNode, signaler);
159
160 return rn;
161}
162
173void VuoRendererComposition::addNode(VuoNode *n, bool nodeShouldBeRendered, bool nodeShouldBeGivenUniqueIdentifier)
174{
175 if (getBase()->hasCompiler() && nodeShouldBeGivenUniqueIdentifier)
177
178 getBase()->addNode(n);
179
180 if (nodeShouldBeRendered)
181 addNodeInCompositionToCanvas(n);
182}
183
192void VuoRendererComposition::addNodeInCompositionToCanvas(VuoNode *n)
193{
195
196 if (renderMissingAsPresent)
197 rn->setMissingImplementation(false);
198
200 addItem(rn);
201
203}
204
211{
212 getBase()->addCable(c);
213 addCableInCompositionToCanvas(c);
214}
215
221void VuoRendererComposition::addCableInCompositionToCanvas(VuoCable *c)
222{
223 VuoRendererCable *rc = (c->hasRenderer() ? c->getRenderer() : new VuoRendererCable(c));
224 addItem(rc);
225
226 // The following VuoRendererCable::setFrom()/setTo() calls are unnecessary as far as the
227 // base cable is concerned, but forces the connected component renderings to update appropriately.
228 rc->setFrom(rc->getBase()->getFromNode(), rc->getBase()->getFromPort());
229 rc->setTo(rc->getBase()->getToNode(), rc->getBase()->getToPort());
230
231 // Performance optimizations
232 rc->setCacheMode(getCurrentDefaultCacheMode());
233}
234
239{
240 return new VuoRendererComment(baseComment, signaler);
241}
242
249{
250 if (getBase()->hasCompiler())
252
253 getBase()->addComment(c);
254 addCommentInCompositionToCanvas(c);
255}
256
262void VuoRendererComposition::addCommentInCompositionToCanvas(VuoComment *c)
263{
265 addItem(rc);
266
267 // Performance optimizations
268 rc->setCacheMode(getCurrentDefaultCacheMode());
269}
270
275{
276 rn->updateGeometry();
277 removeItem(rn);
278
279 getBase()->removeNode(rn->getBase());
280}
281
286{
287 rc->setFrom(NULL, NULL);
288 rc->setTo(NULL, NULL);
289
290 rc->updateGeometry();
291 rc->removeFromScene();
292
293 getBase()->removeCable(rc->getBase());
294}
295
300{
301 rc->updateGeometry();
302 removeItem(rc);
303
304 getBase()->removeComment(rc->getBase());
305}
306
310QList<QGraphicsItem *> VuoRendererComposition::createAndConnectInputAttachments(VuoRendererNode *node, VuoCompiler *compiler, bool createButDoNotAdd)
311{
312 QList<QGraphicsItem *> componentsAttached;
313 if (VuoStringUtilities::beginsWith(node->getBase()->getNodeClass()->getClassName(), "vuo.math.calculate"))
314 {
315 VuoPort *valuesPort = node->getBase()->getInputPortWithName("values");
316 set<VuoRendererInputAttachment *> attachments = valuesPort->getRenderer()->getAllUnderlyingUpstreamInputAttachments();
317 if (attachments.empty())
318 componentsAttached.append(createAndConnectDrawersToReadOnlyDictionaryInputPorts(node, compiler, createButDoNotAdd));
319 }
320
321 componentsAttached.append(createAndConnectDrawersToListInputPorts(node, compiler, createButDoNotAdd));
322
323 return componentsAttached;
324}
325
329QList<QGraphicsItem *> VuoRendererComposition::createAndConnectDrawersToListInputPorts(VuoRendererNode *node, VuoCompiler *compiler, bool createButDoNotAdd)
330{
331 QList<QGraphicsItem *> componentsAttached;
332 foreach (VuoPort *port, node->getBase()->getInputPorts())
333 {
334 VuoCompilerInputEventPort *inputEventPort = (port->hasCompiler()? dynamic_cast<VuoCompilerInputEventPort *>(port->getCompiler()) : NULL);
335 if (inputEventPort && VuoCompilerType::isListType(inputEventPort->getDataType()))
336 {
338 {
339 VuoRendererCable *cable = NULL;
340 VuoRendererNode *makeListNode = createAndConnectMakeListNode(node->getBase(), port, compiler, cable);
341
342 if (cable)
343 componentsAttached.append(cable);
344
345 if (makeListNode)
346 componentsAttached.append(makeListNode);
347
348 if (!createButDoNotAdd)
349 {
350 addNode(makeListNode->getBase());
351 addCable(cable->getBase());
352 }
353 }
354 }
355 }
356
357 return componentsAttached;
358}
359
363QList<QGraphicsItem *> VuoRendererComposition::createAndConnectDrawersToReadOnlyDictionaryInputPorts(VuoRendererNode *node, VuoCompiler *compiler, bool createButDoNotAdd)
364{
365 set<VuoRendererNode *> nodesToAdd;
366 set<VuoRendererCable *> cablesToAdd;
367 createAndConnectDictionaryAttachmentsForNode(node->getBase(), compiler, nodesToAdd, cablesToAdd);
368
369 QList<QGraphicsItem *> componentsAttached;
370 foreach (VuoRendererNode *node, nodesToAdd)
371 componentsAttached.append(node);
372 foreach (VuoRendererCable *cable, cablesToAdd)
373 componentsAttached.append(cable);
374
375 if (!createButDoNotAdd)
376 {
377 foreach (VuoRendererNode *node, nodesToAdd)
378 addNode(node->getBase());
379
380 foreach (VuoRendererCable *cable, cablesToAdd)
381 addCable(cable->getBase());
382 }
383
384 return componentsAttached;
385}
386
397 VuoRendererCable *&rendererCable)
398{
399 VuoRendererNode *makeListRendererNode = NULL;
400 rendererCable = NULL;
401
402 VuoCompilerInputEventPort *inputEventPort = static_cast<VuoCompilerInputEventPort *>(toPort->getCompiler());
403 VuoCompilerType *type = inputEventPort->getDataType();
404
405 vector<string> itemInitialValues;
406 if (inputEventPort->getData())
407 {
408 string listInitialValue = inputEventPort->getData()->getInitialValue();
409 json_object *js = json_tokener_parse(listInitialValue.c_str());
410 if (json_object_get_type(js) == json_type_array)
411 {
412 int itemCount = json_object_array_length(js);
413 for (int i = 0; i < itemCount; ++i)
414 {
415 json_object *itemObject = json_object_array_get_idx(js, i);
416 string itemString = json_object_to_json_string_ext(itemObject, JSON_C_TO_STRING_PLAIN);
417 itemInitialValues.push_back(itemString);
418 }
419 }
420 json_object_put(js);
421 }
422
423 unsigned long itemCount = (itemInitialValues.empty() ? 2 : itemInitialValues.size());
424 string nodeClassName = VuoCompilerMakeListNodeClass::getNodeClassName(itemCount, type);
425 VuoCompilerNodeClass *makeListNodeClass = compiler->getNodeClass(nodeClassName);
426
427 VuoNode *makeListNode = makeListNodeClass->newNode();
428 makeListRendererNode = createRendererNode(makeListNode);
429
430 vector<VuoPort *> itemPorts = makeListNode->getInputPorts();
431 for (size_t i = 0; i < itemInitialValues.size(); ++i)
432 {
434 VuoRendererPort *itemPort = itemPorts[portIndex]->getRenderer();
435 itemPort->setConstant(itemInitialValues[i]);
436 }
437
438 VuoCompilerPort *fromCompilerPort = static_cast<VuoCompilerPort *>(makeListNode->getOutputPorts().back()->getCompiler());
439 VuoCompilerPort *toCompilerPort = static_cast<VuoCompilerPort *>(toPort->getCompiler());
440 VuoCompilerCable *compilerCable = new VuoCompilerCable(makeListNode->getCompiler(), fromCompilerPort,
441 toNode->getCompiler(), toCompilerPort);
442 rendererCable = new VuoRendererCable(compilerCable->getBase());
443
444 return makeListRendererNode;
445}
446
457 VuoCompiler *compiler,
458 set<VuoRendererNode *> &createdNodes,
459 set<VuoRendererCable *> &createdCables)
460{
461 createdNodes.clear();
462 createdCables.clear();
463
464 VuoPort *expressionInputPort = node->getInputPortWithName("expression");
465 VuoPort *valuesInputPort = node->getInputPortWithName("values");
466 if (!(expressionInputPort && valuesInputPort))
467 {
468 VUserLog("Error: Cannot create dictionary attachments for a node without 'expression' and 'values' input ports.");
469 return;
470 }
471
472 // Assume for now that the dictionary should map strings to reals for use as variables in a VuoMathExpressionList.
473 const string dictionaryTypeName = "VuoDictionary_VuoText_VuoReal";
474 const string dictionaryClassName = "vuo.dictionary.make.VuoText.VuoReal";
475 const string dictionaryKeySourceTypeName = "VuoMathExpressionList";
476
477 VuoCompilerPort *valuesInputPortCompiler = static_cast<VuoCompilerPort *>(valuesInputPort->getCompiler());
478 if (valuesInputPortCompiler->getDataVuoType()->getModuleKey() != dictionaryTypeName)
479 {
480 VUserLog("Error: Unexpected dictionary type required: %s", valuesInputPortCompiler->getDataVuoType()->getModuleKey().c_str());
481 return;
482 }
483
484 VuoCompilerPort *expressionInputPortCompiler = static_cast<VuoCompilerPort *>(expressionInputPort->getCompiler());
485 if (expressionInputPortCompiler->getDataVuoType()->getModuleKey() != dictionaryKeySourceTypeName)
486 {
487 VUserLog("Error: Unexpected key source type encountered: %s", expressionInputPortCompiler->getDataVuoType()->getModuleKey().c_str());
488 return;
489 }
490
491 // Extract the variable names from the math expressions.
492 VuoCompilerInputEventPort *expressionInputEventPort = static_cast<VuoCompilerInputEventPort *>(expressionInputPortCompiler);
493 string expressionConstant = expressionInputEventPort->getData()->getInitialValue();
494 vector<string> inputVariables = extractInputVariableListFromExpressionsConstant(expressionConstant, node->getNodeClass()->getClassName());
495 unsigned long itemCount = inputVariables.size();
496
497 string keyListClassName = VuoCompilerMakeListNodeClass::buildNodeClassName(itemCount, "VuoText");
498 string valueListClassName = VuoCompilerMakeListNodeClass::buildNodeClassName(itemCount, "VuoReal");
499
500 VuoCompilerNodeClass *keyListNodeClass = compiler->getNodeClass(keyListClassName);
501 VuoCompilerNodeClass *valueListNodeClass = compiler->getNodeClass(valueListClassName);
502 VuoCompilerNodeClass *dictionaryNodeClass = compiler->getNodeClass(dictionaryClassName);
503
504 if (keyListNodeClass && valueListNodeClass && dictionaryNodeClass)
505 {
506 // Create and connect all base components before creating any renderer components so that createRendererNode()
507 // has all of the information it needs to create the appropriate renderer form for each node.
508 QPoint offset(-220, 50);
509 VuoNode *dictionaryNode = compiler->createNode(dictionaryNodeClass, "", node->getX()+offset.x(), node->getY()+offset.y());
510 VuoNode *keyListNode = compiler->createNode(keyListNodeClass, "", node->getX()+offset.x(), node->getY()+offset.y());
511 VuoNode *valueListNode = compiler->createNode(valueListNodeClass, "", node->getX()+offset.x(), node->getY()+offset.y());
512
513 VuoCable *cableCarryingDictionary = (new VuoCompilerCable(dictionaryNode->getCompiler(),
514 static_cast<VuoCompilerPort *>(dictionaryNode->getOutputPortWithName("dictionary")->getCompiler()),
515 node->getCompiler(),
516 static_cast<VuoCompilerPort *>(valuesInputPort->getCompiler())))->getBase();
517
518 VuoCable *cableCarryingKeys = (new VuoCompilerCable(keyListNode->getCompiler(),
519 static_cast<VuoCompilerPort *>(keyListNode->getOutputPortWithName("list")->getCompiler()),
520 dictionaryNode->getCompiler(),
521 static_cast<VuoCompilerPort *>(dictionaryNode->getInputPortWithName("keys")->getCompiler())))->getBase();
522
523 VuoCable *cableCarryingValues = (new VuoCompilerCable(valueListNode->getCompiler(),
524 static_cast<VuoCompilerPort *>(valueListNode->getOutputPortWithName("list")->getCompiler()),
525 dictionaryNode->getCompiler(),
526 static_cast<VuoCompilerPort *>(dictionaryNode->getInputPortWithName("values")->getCompiler())))->getBase();
527
528 // Set the variable names extracted from the math expressions.
529 vector<VuoPort *> keyPorts = keyListNode->getInputPorts();
530 for (size_t i = 0; i < itemCount; ++i)
531 {
533 string key = "\"" + inputVariables[i] + "\"";
534
535 VuoCompilerInputEventPort *keyEventPort = static_cast<VuoCompilerInputEventPort *>(keyPort->getCompiler());
536 keyEventPort->getData()->setInitialValue(key);
537 }
538
539 createdNodes.insert(createRendererNode(dictionaryNode));
540 createdNodes.insert(createRendererNode(keyListNode));
541 createdNodes.insert(createRendererNode(valueListNode));
542
543 createdCables.insert(new VuoRendererCable(cableCarryingDictionary));
544 createdCables.insert(new VuoRendererCable(cableCarryingKeys));
545 createdCables.insert(new VuoRendererCable(cableCarryingValues));
546 }
547}
548
555vector<string> VuoRendererComposition::extractInputVariableListFromExpressionsConstant(string constant, string nodeClassName)
556{
557 vector<string> inputVariables;
558
559 json_object *js = json_tokener_parse(constant.c_str());
560 json_object *expressionsObject = NULL;
561
562 if (json_object_object_get_ex(js, "inputVariables", &expressionsObject))
563 {
564 if (json_object_get_type(expressionsObject) == json_type_array)
565 {
566 int variableCount = json_object_array_length(expressionsObject);
567 for (int i = 0; i < variableCount; ++i)
568 {
569 json_object *itemObject = json_object_array_get_idx(expressionsObject, i);
570 if (json_object_get_type(itemObject) == json_type_string)
571 {
572 const char *variableName = json_object_get_string(itemObject);
573 if (nodeClassName == "vuo.math.calculate.list"
574 && (strcasecmp(variableName, "x") == 0
575 || strcasecmp(variableName, "i") == 0))
576 continue;
577
578 inputVariables.push_back(json_object_get_string(itemObject));
579 }
580 }
581 }
582 }
583 json_object_put(js);
584
585 return inputVariables;
586}
587
593{
594 if (! publishedPort->hasCompiler())
595 return NULL;
596
597 return new VuoRendererPublishedPort(publishedPort, !isPublishedInput);
598}
599
603void VuoRendererComposition::addPublishedPort(VuoPublishedPort *publishedPort, bool isPublishedInput, VuoCompiler *compiler)
604{
605 string name = publishedPort->getClass()->getName();
606 if (isPublishedInput)
607 {
609 if (! existingPort)
610 {
611 int index = getBase()->getPublishedInputPorts().size();
612 getBase()->addPublishedInputPort(publishedPort, index);
613 }
614 else if (publishedPort != existingPort)
615 VUserLog("Error: Unhandled published port name conflict.");
616 }
617 else // if (! isPublishedInput)
618 {
620 if (! existingPort)
621 {
622 int index = getBase()->getPublishedOutputPorts().size();
623 getBase()->addPublishedOutputPort(publishedPort, index);
624 }
625 else if (publishedPort != existingPort)
626 VUserLog("Error: Unhandled published port name conflict.");
627 }
628}
629
636int VuoRendererComposition::removePublishedPort(VuoPublishedPort *publishedPort, bool isPublishedInput, VuoCompiler *compiler)
637{
638 if (isPublishedInput)
639 {
640 int index = getBase()->getIndexOfPublishedPort(publishedPort, isPublishedInput);
641 if (index != -1)
643
644 return index;
645 }
646 else
647 {
648 int index = getBase()->getIndexOfPublishedPort(publishedPort, isPublishedInput);
649 if (index != -1)
651
652 return index;
653 }
654}
655
661{
662 publishedPort->setName(getUniquePublishedPortName(name));
663}
664
670VuoNode * VuoRendererComposition::createPublishedInputNode()
671{
672 VuoNodeClass *dummyPublishedInputNodeClass = new VuoNodeClass(VuoNodeClass::publishedInputNodeClassName, vector<string>(), vector<string>());
673 return dummyPublishedInputNodeClass->newNode(VuoNodeClass::publishedInputNodeIdentifier);
674}
675
681VuoNode * VuoRendererComposition::createPublishedOutputNode()
682{
683 VuoNodeClass *dummyPublishedOutputNodeClass = new VuoNodeClass(VuoNodeClass::publishedOutputNodeClassName, vector<string>(), vector<string>());
684 return dummyPublishedOutputNodeClass->newNode(VuoNodeClass::publishedOutputNodeIdentifier);
685}
686
692{
693 auto isNameAvailable = [this] (const string &name)
694 {
695 return (! name.empty() &&
696 name != "refresh" &&
697 getBase()->getPublishedInputPortWithName(name) == nullptr &&
698 getBase()->getPublishedOutputPortWithName(name) == nullptr);
699 };
700
701 return VuoStringUtilities::formUniqueIdentifier(isNameAvailable, baseName);
702}
703
708{
709 return (!port->getPublishedPorts().empty());
710}
711
717{
718 vector<VuoRendererNode *> typecastsCollapsed;
719
720 foreach (VuoNode *node, getBase()->getNodes())
721 {
722 if (node->isTypecastNode())
723 {
725 if (collapsedPort)
726 typecastsCollapsed.push_back(node->getRenderer());
727 }
728 }
729
730 return typecastsCollapsed;
731}
732
738{
739 // Don't try to collapse nodes that don't qualify as typecasts.
740 if (!rn || !rn->getBase()->isTypecastNode())
741 return NULL;
742
745 return NULL;
746
749
750 // Don't try to re-collapse typecasts that are already collapsed.
751 VuoRendererPort *typecastParent = typecastInPort->getRenderer()->getTypecastParentPort();
752 if (typecastParent)
753 return NULL;
754
755 vector<VuoCable *> outCables = typecastOutPort->getConnectedCables(true);
756 vector<VuoCable *> inCables = typecastInPort->getConnectedCables(true);
757
758 // Don't try to collapse typecast nodes with incoming cables to the "refresh" port.
759 if ( ! rn->getBase()->getRefreshPort()->getConnectedCables(true).empty() )
760 return NULL;
761
762 // Don't try to collapse typecast nodes outputting to multiple nodes, or without any output cables.
763 if (outCables.size() != 1)
764 return NULL;
765
766 VuoCable *outCable = *(outCables.begin());
767
768 // Don't try to collapse typecast nodes whose outgoing cable is event-only.
769 if (!(outCable->hasRenderer() && outCable->getRenderer()->effectivelyCarriesData()))
770 return NULL;
771
772 // Don't try to collapse typecast nodes without any incoming data+event cables (including published ones).
773 VuoCable *incomingDataCable = NULL;
774 for (vector<VuoCable *>::iterator i = inCables.begin(); !incomingDataCable && (i != inCables.end()); ++i)
775 {
776 if ((*i)->hasRenderer() && (*i)->getRenderer()->effectivelyCarriesData())
777 incomingDataCable = *i;
778 }
779 if (! incomingDataCable)
780 return NULL;
781
782 // Don't try to collapse typecast nodes that have published output ports.
783 if (outCable->isPublished())
784 return NULL;
785
786 VuoNode *fromNode = incomingDataCable->getFromNode();
787 VuoPort *fromPort = incomingDataCable->getFromPort();
788
789 VuoNode * toNode = outCable->getToNode();
790 VuoPort * toPort = outCable->getToPort();
791
792 // Don't try to collapse typecast nodes with input or output cables that are not currently connected at both ends.
793 if (! (fromNode && fromPort && toNode && toPort))
794 return NULL;
795
796 // Don't try to collapse typecast nodes with attached input drawers.
797 if (rn->getAttachedInputDrawers().size() > 0)
798 return NULL;
799
800 // Don't try to collapse typecast nodes outputting to other typecasts.
801 if (toNode->isTypecastNode())
802 return NULL;
803
804 // Don't try to collapse typecast nodes outputting to ports with multiple input cables.
805 if (toPort->getConnectedCables(false).size() > 1)
806 return NULL;
807
808 // Don't try to collapse typecast nodes outputting to internal ports that have been published.
809 if (isPortPublished(toPort->getRenderer()))
810 return NULL;
811
812 // Hide the typecast node.
813 VuoRendererNode * toRN = toNode->getRenderer();
814 rn->updateGeometry();
815 rn->setProxyNode(toRN);
816
817 // Replace the target node's input port with a new typecast port.
818 VuoRendererPort * oldToRP = toPort->getRenderer();
820 oldToRP,
821 signaler);
822
823 QGraphicsItem::CacheMode defaultCacheMode = getCurrentDefaultCacheMode();
824 foreach (VuoCable *cable, inCables)
825 {
826 if (cable->hasRenderer())
827 {
828 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
829 cable->getRenderer()->updateGeometry();
830 cable->getRenderer()->setCacheMode(defaultCacheMode);
831 }
832 }
833
834 foreach (VuoCable *cable, outCables)
835 {
836 if (cable->hasRenderer())
837 {
838 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
839 cable->getRenderer()->updateGeometry();
840 cable->getRenderer()->setCacheMode(defaultCacheMode);
841 }
842 }
843
844 tp->updateGeometry();
845 toRN->replaceInputPort(oldToRP, tp);
847 typecastInPort->getRenderer()->setTypecastParentPort(tp);
848 typecastInPort->getRenderer()->setParentItem(toRN);
849
850 // Notify the base port of the change in renderer port, to reverse
851 // the change made within the VuoRendererPort constructor on behalf of any
852 // renderer port previously initialized for this base port.
853 tp->getBase()->setRenderer(tp);
854
855 // The typecast port may have been added to a drawer,
856 // and may thus need to change the horizontal position of its name.
857 tp->updateNameRect();
858
860
861 return tp;
862}
863
868{
869 foreach (VuoNode *node, getBase()->getNodes())
870 {
871 if (node->isTypecastNode())
872 {
875 }
876 }
877}
878
884{
885 VuoPort * typecastInPort = typecastNode->getBase()->getInputPorts()[VuoNodeClass::unreservedInputPortStartIndex];
886 VuoRendererPort *typecastParent = typecastInPort->getRenderer()->getTypecastParentPort();
887
888 if (typecastParent)
890}
891
896{
897 VuoRendererNode *uncollapsedNode = typecast->getUncollapsedTypecastNode();
898 VuoPort *typecastInPort = uncollapsedNode->getBase()->getInputPorts()[VuoNodeClass::unreservedInputPortStartIndex];
899 VuoPort *typecastOutPort = uncollapsedNode->getBase()->getOutputPorts()[VuoNodeClass::unreservedOutputPortStartIndex];
900 vector<VuoCable *> outCables = typecastOutPort->getConnectedCables(false);
901 VuoCable * outCable = *(outCables.begin());
902 VuoRendererPort *uncollapsedToRP = typecast->getReplacedPort();
903 VuoRendererNode *toRN = outCable->getToNode()->getRenderer();
904
905 QGraphicsItem::CacheMode defaultCacheMode = getCurrentDefaultCacheMode();
906 foreach (VuoCable *cable, typecastInPort->getConnectedCables(true))
907 {
908 if (cable->hasRenderer())
909 {
910 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
911 cable->getRenderer()->updateGeometry();
912 cable->getRenderer()->setCacheMode(defaultCacheMode);
913 }
914 }
915
916 foreach (VuoCable *cable, typecastOutPort->getConnectedCables(true))
917 {
918 if (cable->hasRenderer())
919 {
920 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
921 cable->getRenderer()->updateGeometry();
922 cable->getRenderer()->setCacheMode(defaultCacheMode);
923 }
924 }
925
926 typecast->updateGeometry();
927 uncollapsedNode->updateGeometry();
928
929 typecastInPort->getRenderer()->setParentItem(uncollapsedNode);
930 uncollapsedNode->addInputPort(typecastInPort->getRenderer());
931
932 typecastInPort->getRenderer()->setTypecastParentPort(NULL);
933 uncollapsedNode->setProxyNode(NULL);
934
935 toRN->updateGeometry();
936 toRN->replaceInputPort(typecast, uncollapsedToRP);
938
939 // Notify the base port of the change in renderer port, to reverse
940 // the change made within the VuoRendererPort constructor on behalf of any
941 // renderer port previously initialized for this base port.
942 uncollapsedToRP->getBase()->setRenderer(uncollapsedToRP);
943
944 toRN->layoutConnectedInputDrawersAtAndAbovePort(uncollapsedToRP);
945
946 return uncollapsedNode;
947}
948
953{
954 return this->publishedInputNode;
955}
956
961{
962 return this->publishedOutputNode;
963}
964
969{
970 foreach (VuoNode *node, getBase()->getNodes())
971 {
972 vector<VuoPort *> inputPorts = node->getInputPorts();
973 for(vector<VuoPort *>::iterator inputPort = inputPorts.begin(); inputPort != inputPorts.end(); ++inputPort)
974 {
975 QGraphicsItem::CacheMode normalCacheMode = (*inputPort)->getRenderer()->cacheMode();
976 (*inputPort)->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
977
978 (*inputPort)->getRenderer()->updateGeometry();
979 (*inputPort)->getRenderer()->setEligibilityHighlight(VuoRendererColors::noHighlight);
980
981 (*inputPort)->getRenderer()->setCacheMode(normalCacheMode);
982
983 VuoRendererTypecastPort *typecastPort = dynamic_cast<VuoRendererTypecastPort *>((*inputPort)->getRenderer());
984 if (typecastPort)
985 {
986 QGraphicsItem::CacheMode normalCacheMode = typecastPort->getChildPort()->cacheMode();
987 typecastPort->getChildPort()->setCacheMode(QGraphicsItem::NoCache);
988
989 typecastPort->getChildPort()->updateGeometry();
991
992 typecastPort->getChildPort()->setCacheMode(normalCacheMode);
993 }
994 }
995
996 vector<VuoPort *> outputPorts = node->getOutputPorts();
997 for(vector<VuoPort *>::iterator outputPort = outputPorts.begin(); outputPort != outputPorts.end(); ++outputPort)
998 {
999 QGraphicsItem::CacheMode normalCacheMode = (*outputPort)->getRenderer()->cacheMode();
1000 (*outputPort)->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
1001
1002 (*outputPort)->getRenderer()->updateGeometry();
1003 (*outputPort)->getRenderer()->setEligibilityHighlight(VuoRendererColors::noHighlight);
1004
1005 (*outputPort)->getRenderer()->setCacheMode(normalCacheMode);
1006 }
1007
1008
1009 VuoRendererNode *rn = node->getRenderer();
1010 QGraphicsItem::CacheMode normalCacheMode = rn->cacheMode();
1011 rn->setCacheMode(QGraphicsItem::NoCache);
1012
1013 rn->updateGeometry();
1015
1016 rn->setCacheMode(normalCacheMode);
1017 }
1018
1019 foreach (VuoCable *cable, getBase()->getCables())
1020 {
1021 VuoRendererCable *rc = cable->getRenderer();
1022 QGraphicsItem::CacheMode normalCacheMode = rc->cacheMode();
1023 rc->setCacheMode(QGraphicsItem::NoCache);
1024 rc->updateGeometry();
1025
1027
1028 rc->setCacheMode(normalCacheMode);
1029 }
1030}
1031
1036{
1037 foreach (VuoNode *node, getBase()->getNodes())
1038 {
1039 node->getRenderer()->updateGeometry();
1040
1041 foreach (VuoPort *port, node->getInputPorts())
1042 port->getRenderer()->updateGeometry();
1043
1044 foreach (VuoPort *port, node->getOutputPorts())
1045 port->getRenderer()->updateGeometry();
1046 }
1047
1048 foreach (VuoCable *cable, getBase()->getCables())
1049 cable->getRenderer()->updateGeometry();
1050}
1051
1058{
1059 return this->renderNodeActivity;
1060}
1061
1068{
1069 return (this->renderNodeActivity && this->renderPortActivity);
1070}
1071
1078void VuoRendererComposition::drawBackground(QPainter *painter, const QRectF &rect)
1079{
1080 QGraphicsScene::drawBackground(painter, rect);
1081
1083 {
1084 painter->setRenderHint(QPainter::Antialiasing, true);
1085 painter->setPen(QPen(QColor(255,0,0,128),5));
1086 painter->drawRect(sceneRect());
1087 QVector<QPointF> points;
1088 points << QPointF(0,0);
1089 points << sceneRect().topLeft();
1090 points << sceneRect().center();
1091 points << sceneRect().bottomRight();
1092 foreach (QPointF p, points)
1093 {
1094 painter->setPen(QPen(QColor(255,0,0,128),5));
1095 painter->drawEllipse(p, 5, 5);
1096 painter->setPen(QColor(255,0,0,128));
1097 painter->drawText(p + QPointF(5,15) - (p == sceneRect().bottomRight() ? QPointF(70,20) : QPointF(0,0)), QString("(%1,%2)").arg(p.x()).arg(p.y()));
1098 }
1099 painter->setRenderHint(QPainter::Antialiasing, false);
1100 }
1101
1102 // Draw grid.
1103 if (gridType != NoGrid)
1104 {
1106
1107 qreal leftmostGridLine = quantizeToNearestGridLine(rect.topLeft(), gridSpacing).x();
1108 if (leftmostGridLine < rect.left())
1109 leftmostGridLine += gridSpacing;
1110 qreal topmostGridLine = quantizeToNearestGridLine(rect.topLeft(), gridSpacing).y();
1111 if (topmostGridLine < rect.top())
1112 topmostGridLine += gridSpacing;
1113
1114 // Correct for the fact that VuoRendererNode::paint() starts painting at (-1,0) rather than (0,0).
1115 // @todo: Eliminate this correction after modifying VuoRendererNode::paint()
1116 // for https://b33p.net/kosada/node/10210 .
1117 const int nodeXAlignmentCorrection = -1;
1118
1119 if (gridType == LineGrid)
1120 {
1121 QVector<QLineF> gridLines;
1122 for (qreal x = leftmostGridLine; x < rect.right(); x += gridSpacing)
1123 gridLines.append(QLineF(x + nodeXAlignmentCorrection, rect.top(), x + nodeXAlignmentCorrection, rect.bottom()));
1124 for (qreal y = topmostGridLine; y < rect.bottom(); y += gridSpacing)
1125 gridLines.append(QLineF(rect.left(), y, rect.right(), y));
1126
1127 painter->setPen(QColor(128, 128, 128, VuoRendererComposition::gridOpacity * 32));
1128 painter->drawLines(gridLines);
1129 }
1130 else if (gridType == PointGrid)
1131 {
1132 painter->setRenderHint(QPainter::Antialiasing, true);
1133 painter->setPen(Qt::NoPen);
1134 painter->setBrush(QColor(128, 128, 128, VuoRendererComposition::gridOpacity * 128));
1135
1136 for (qreal y = topmostGridLine; y < rect.bottom(); y += gridSpacing)
1137 for (qreal x = leftmostGridLine; x < rect.right(); x += gridSpacing)
1138 // Offset by a half-pixel to render a softer plus-like point (instead of a sharp pixel-aligned square).
1139 painter->drawEllipse(QPointF(x + nodeXAlignmentCorrection + .5, y + .5), 1.5,1.5);
1140 }
1141 }
1142}
1143
1150void VuoRendererComposition::setRenderActivity(bool render, bool includePortActivity)
1151{
1152 if ((this->renderNodeActivity == render) && (this->renderPortActivity == includePortActivity))
1153 return;
1154
1155 this->renderNodeActivity = render;
1156 this->renderPortActivity = includePortActivity;
1157
1158 if (render)
1159 {
1160 foreach (VuoNode *node, getBase()->getNodes())
1161 {
1163
1164 if (includePortActivity)
1165 {
1166 foreach (VuoPort *port, node->getInputPorts())
1168
1169 foreach (VuoPort *port, node->getOutputPorts())
1171 }
1172 }
1173
1174 foreach (VuoCable *cable, getBase()->getCables())
1176 }
1177
1180}
1181
1186{
1187 return this->renderHiddenCables;
1188}
1189
1194{
1195 setComponentCaching(QGraphicsItem::NoCache);
1196 this->renderHiddenCables = render;
1199}
1200
1205void VuoRendererComposition::setComponentCaching(QGraphicsItem::CacheMode cacheMode)
1206{
1207 // Nodes and ports
1208 foreach (VuoNode *node, getBase()->getNodes())
1209 {
1210 VuoRendererNode *rn = node->getRenderer();
1211 if (rn)
1212 rn->setCacheModeForNodeAndPorts(cacheMode);
1213 }
1214
1215 // Cables
1216 set<VuoCable *> allCables = getBase()->getCables();
1217 foreach (VuoCable *cable, allCables)
1218 {
1219 VuoRendererCable *rc = cable->getRenderer();
1220 if (rc)
1221 rc->setCacheMode(cacheMode);
1222 }
1223}
1224
1231{
1232 return ((cachingEnabled && !renderNodeActivity)? QGraphicsItem::DeviceCoordinateCache : QGraphicsItem::NoCache);
1233}
1234
1240{
1241#ifdef __APPLE__
1242 // [NSAutoreleasePool new];
1243 Class poolClass = objc_getClass("NSAutoreleasePool");
1244 ((void (*)(id, SEL))objc_msgSend)((id)poolClass, sel_getUid("new"));
1245#endif
1246}
1247
1254{
1255 foreach (VuoNode *node, getBase()->getNodes())
1256 {
1257 foreach (VuoPort *port, node->getInputPorts())
1258 {
1259 if (!port->hasRenderer())
1260 continue;
1261
1262 VuoRendererPort *rp = port->getRenderer();
1264 {
1266 VuoRetain(url);
1267 string modifiedRelativeResourcePath = modifyResourcePathForBundle(url);
1268 rp->setConstant("\"" + modifiedRelativeResourcePath + "\"");
1269 VuoRelease(url);
1270 }
1271 }
1272 }
1273}
1274
1281{
1282 map<VuoPort *, string> modifiedPathForPort;
1283 foreach (VuoNode *node, getBase()->getNodes())
1284 {
1285 foreach (VuoPort *port, node->getInputPorts())
1286 {
1287 if (!port->hasRenderer())
1288 continue;
1289
1290 VuoRendererPort *rp = port->getRenderer();
1292 {
1294 VuoRetain(url);
1295 QString origRelativeResourcePath = url;
1296 string modifiedRelativeResourcePath = modifyResourcePathForNewDir(origRelativeResourcePath.toUtf8().constData(), newDir);
1297
1298 if (modifiedRelativeResourcePath != origRelativeResourcePath.toUtf8().constData())
1299 modifiedPathForPort[port] = "\"" + modifiedRelativeResourcePath + "\"";
1300 VuoRelease(url);
1301 }
1302 }
1303 }
1304
1305 return modifiedPathForPort;
1306}
1307
1313{
1314 string iconURL = getBase()->getMetadata()->getIconURL();
1315 string quotedIconURL = "\"" + iconURL + "\"";
1316
1317 VuoUrl url = VuoUrl_makeFromString(quotedIconURL.c_str());
1318 VuoRetain(url);
1319 bool isRelativePath = VuoUrl_isRelativePath(url);
1320 QString origRelativeResourcePath = url;
1321 VuoRelease(url);
1322
1323 if (!isRelativePath)
1324 return iconURL;
1325
1326 return modifyResourcePathForNewDir(origRelativeResourcePath.toUtf8().constData(), newDir);
1327}
1328
1339void VuoRendererComposition::bundleResourceFiles(string targetResourceDir, bool tmpFilesOnly, QString bundledIconPath)
1340{
1341 // Update relative URLs referenced within port constants.
1342 foreach (VuoNode *node, getBase()->getNodes())
1343 {
1344 foreach (VuoPort *port, node->getInputPorts())
1345 {
1346 if (!port->hasRenderer())
1347 continue;
1348
1349 VuoRendererPort *rp = port->getRenderer();
1351 {
1353 VuoRetain(url);
1354 QString origRelativeResourcePath = url;
1355 QString modifiedRelativeResourcePath = modifyResourcePathForBundle(origRelativeResourcePath.toUtf8().constData()).c_str();
1356
1357 string origRelativeDir, modifiedRelativeDir, file, ext;
1358 VuoFileUtilities::splitPath(origRelativeResourcePath.toUtf8().constData(), origRelativeDir, file, ext);
1359 VuoFileUtilities::splitPath(modifiedRelativeResourcePath.toUtf8().constData(), modifiedRelativeDir, file, ext);
1360 string resourceFileName = file;
1361 if (!ext.empty())
1362 {
1363 resourceFileName += ".";
1364 resourceFileName += ext;
1365 }
1366
1367 QDir compositionDir(QDir(getBase()->getDirectory().c_str()).canonicalPath());
1368 QDir appDir(QDir(targetResourceDir.c_str()).canonicalPath());
1369
1370 QString sourceFilePath = compositionDir.filePath(QDir(origRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1371 if (!tmpFilesOnly || isTmpFile(sourceFilePath.toUtf8().constData()))
1372 {
1373 if (!modifiedRelativeDir.empty())
1374 appDir.mkpath(modifiedRelativeDir.c_str());
1375
1376 QString targetFilePath = appDir.filePath(QDir(modifiedRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1377
1378 VDebugLog("Copying \"%s\" (from %s:%s)", url, node->getTitle().c_str(), port->getClass()->getName().c_str());
1379 VuoFileUtilities::copyDirectory(sourceFilePath.toStdString(), targetFilePath.toStdString());
1380
1381 if (VuoFileType_isFileOfType(sourceFilePath.toUtf8().constData(), VuoFileType_Scene))
1382 bundleAuxiliaryFilesForSceneFile(sourceFilePath, targetFilePath);
1383 }
1384 VuoRelease(url);
1385 }
1386
1387 }
1388 }
1389
1390 // Update relative path to custom icon.
1391 string iconURL = getBase()->getMetadata()->getIconURL();
1392 string quotedIconURL = "\"" + iconURL + "\"";
1393
1394 VuoUrl url = VuoUrl_makeFromString(quotedIconURL.c_str());
1395 VuoRetain(url);
1396 bool iconHasRelativePath = VuoUrl_isRelativePath(url);
1397 QString origRelativeResourcePath = url;
1398
1399 if (iconHasRelativePath)
1400 {
1401 QString modifiedRelativeResourcePath = modifyResourcePathForBundle(origRelativeResourcePath.toUtf8().constData()).c_str();
1402
1403 string origRelativeDir, modifiedRelativeDir, file, ext;
1404 VuoFileUtilities::splitPath(origRelativeResourcePath.toUtf8().constData(), origRelativeDir, file, ext);
1405 VuoFileUtilities::splitPath(modifiedRelativeResourcePath.toUtf8().constData(), modifiedRelativeDir, file, ext);
1406 string resourceFileName = file;
1407 if (!ext.empty())
1408 {
1409 resourceFileName += ".";
1410 resourceFileName += ext;
1411 }
1412
1413 QDir compositionDir(QDir(getBase()->getDirectory().c_str()).canonicalPath());
1414 QDir appDir(QDir(targetResourceDir.c_str()).canonicalPath());
1415
1416 QString sourceFilePath = compositionDir.filePath(QDir(origRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1417 if (!tmpFilesOnly || isTmpFile(sourceFilePath.toUtf8().constData()))
1418 {
1419 if (!modifiedRelativeDir.empty())
1420 appDir.mkpath(modifiedRelativeDir.c_str());
1421
1422 QString targetFilePath = (!bundledIconPath.isEmpty()? bundledIconPath :
1423 appDir.filePath(QDir(modifiedRelativeDir.c_str()).filePath(resourceFileName.c_str())));
1424
1425 // Case: Icon is already in .icns format; copy it directly.
1426 if (QString(ext.c_str()).toLower() == "icns")
1427 {
1428 VDebugLog("Copying \"%s\" (app icon)", url);
1429 VuoFileUtilities::copyDirectory(sourceFilePath.toStdString(), targetFilePath.toStdString());
1430 }
1431 // Case: Icon is in some other format; convert it to an ".icns" file.
1432 else
1433 {
1434 VDebugLog("Converting \"%s\" (app icon)", url);
1435 QPixmap icon(sourceFilePath);
1436 QFile file(targetFilePath);
1437 file.open(QIODevice::WriteOnly);
1438 icon.save(&file, "ICNS");
1439 }
1440 }
1441 }
1442 VuoRelease(url);
1443
1444 // @todo https://b33p.net/kosada/node/9205 : Published input port constants?
1445}
1446
1452{
1453 const QString tmpDirPath = QDir(VuoFileUtilities::getTmpDir().c_str()).canonicalPath();
1454 QDir parentDir(QDir(QFileInfo(filePath.c_str()).dir()).canonicalPath());
1455
1456 do
1457 {
1458 if (parentDir.canonicalPath() == tmpDirPath)
1459 return true;
1460 } while (parentDir.cdUp());
1461
1462 return false;
1463}
1464
1469string VuoRendererComposition::modifyResourcePathForBundle(string path)
1470{
1471 if (VuoUrl_isRelativePath(path.c_str()))
1472 {
1473 // Replace parent-directory indicators ("../") so that resources
1474 // located within the parent (or ancestor) directory of the original composition will
1475 // still be copied into the exported app's "Resources" directory or a subdirectory thereof.
1476 const QString originalParentDirIndicator = "/../";
1477 const QString appBundleParentDirIndicator = "/VuoParentDir/";
1478
1479 QString modifiedPath = QString("/").append(path.c_str());
1480
1481 while (modifiedPath.contains(originalParentDirIndicator))
1482 modifiedPath.replace(originalParentDirIndicator, appBundleParentDirIndicator);
1483
1484 modifiedPath.remove(0, 1); // Remove leading '/' added earlier
1485
1486 return modifiedPath.toUtf8().constData();
1487 }
1488
1489 else
1490 return path;
1491}
1492
1497string VuoRendererComposition::modifyResourcePathForNewDir(string path, QDir newDir)
1498{
1499 if (VuoUrl_isRelativePath(path.c_str()))
1500 {
1501 QDir compositionDir(QDir(getBase()->getDirectory().c_str()).canonicalPath());
1502 if (compositionDir.canonicalPath() == newDir.canonicalPath())
1503 return path;
1504
1505 string origRelativeDir, file, ext;
1506 VuoFileUtilities::splitPath(path, origRelativeDir, file, ext);
1507 string resourceFileName = file;
1508 if (!ext.empty())
1509 {
1510 resourceFileName += ".";
1511 resourceFileName += ext;
1512 }
1513
1514 QString sourceFilePath = compositionDir.filePath(QDir(origRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1515 string modifiedPath = newDir.relativeFilePath(sourceFilePath).toUtf8().constData();
1516
1517 return modifiedPath;
1518 }
1519
1520 else
1521 return path;
1522}
1523
1528void VuoRendererComposition::bundleAuxiliaryFilesForSceneFile(QString sourceFilePath, QString targetFilePath)
1529{
1530 string sourceDirName, targetDirName, file, ext;
1531 VuoFileUtilities::splitPath(sourceFilePath.toUtf8().constData(), sourceDirName, file, ext);
1532 VuoFileUtilities::splitPath(targetFilePath.toUtf8().constData(), targetDirName, file, ext);
1533
1534 // Bundle any file or folder in the same directory as the mesh file
1535 // whose name begins with the mesh file's basename.
1536 QDir sourceDir(sourceDirName.c_str());
1537 QStringList filesWithMatchingBaseName = QStringList() << QString(file.c_str()).append("*");
1538 sourceDir.setNameFilters(filesWithMatchingBaseName);
1539
1540 foreach (QString auxiliaryFile, sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot))
1541 {
1542 QString sourceFile = QString(sourceDirName.c_str()) + QDir::separator() + auxiliaryFile;
1543 QString targetFile = QString(targetDirName.c_str()) + QDir::separator() + auxiliaryFile;
1544 if (!QFileInfo(targetFile).exists())
1545 {
1546 VDebugLog("Copying \"%s\"", QFileInfo(targetFile).fileName().toUtf8().constData());
1547 VuoFileUtilities::copyDirectory(sourceFile.toStdString(), targetFile.toStdString());
1548 }
1549 }
1550
1551 // Bundle texture folders in the same directory as the mesh file.
1552 QStringList textureFolderNames = QStringList() << "Textures" << "_Textures";
1553 sourceDir.setNameFilters(textureFolderNames);
1554 foreach (QString textureFolderName, sourceDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
1555 {
1556 QString sourceTextureDir = QString(sourceDirName.c_str()) + QDir::separator() + textureFolderName;
1557 QString targetTextureDir = QString(targetDirName.c_str()) + QDir::separator() + textureFolderName;
1558 if (!QFileInfo(targetTextureDir).exists())
1559 {
1560 VDebugLog("Copying \"%s\"", QFileInfo(targetTextureDir).fileName().toUtf8().constData());
1561 VuoFileUtilities::copyDirectory(sourceTextureDir.toStdString(), targetTextureDir.toStdString());
1562 }
1563 }
1564
1565 // @todo: Bundle texture folders in the parent directory of the mesh file.
1566 // See https://b33p.net/kosada/node/9390, https://b33p.net/kosada/node/9391.
1567
1568 return;
1569}
1570
1576{
1577 QDir dir(path.c_str());
1578 return dir.exists() && !VuoFileType_isFileOfType(path.c_str(), VuoFileType_App);
1579}
1580
1585{
1586 VuoRendererComposition::gridOpacity = opacity;
1587}
1588
1593{
1594 return VuoRendererComposition::gridOpacity;
1595}
1596
1601{
1602 VuoRendererComposition::gridType = type;
1603}
1604
1609QPoint VuoRendererComposition::quantizeToNearestGridLine(QPointF point, int gridSpacing)
1610{
1611 return QPoint(floor((point.x()/(1.0*gridSpacing))+0.5)*gridSpacing,
1612 floor((point.y()/(1.0*gridSpacing))+0.5)*gridSpacing);
1613}