1 | // Copyright (C) 2019 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3dloader_p.h" |
5 | |
6 | #include "qquick3dobject_p.h" |
7 | |
8 | #include <QtQml/qqmlinfo.h> |
9 | |
10 | #include <private/qqmlengine_p.h> |
11 | #include <private/qqmlglobal_p.h> |
12 | |
13 | #include <private/qqmlcomponent_p.h> |
14 | #include <private/qqmlincubator_p.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | void QQuick3DLoaderIncubator::statusChanged(QQmlIncubator::Status status) |
19 | { |
20 | m_loader->incubatorStateChanged(status); |
21 | } |
22 | |
23 | void QQuick3DLoaderIncubator::setInitialState(QObject *o) |
24 | { |
25 | m_loader->setInitialState(o); |
26 | } |
27 | |
28 | /*! |
29 | \qmltype Loader3D |
30 | \inqmlmodule QtQuick3D |
31 | \inherits Node |
32 | |
33 | \brief Allows dynamic loading of a 3D subtree from a URL or Component. |
34 | |
35 | Loader3D is used to dynamically load QML components for Qt Quick 3D. |
36 | |
37 | Loader3D can load a |
38 | QML file (using the \l source property) or a \l Component object (using |
39 | the \l sourceComponent property). It is useful for delaying the creation |
40 | of a component until it is required: for example, when a component should |
41 | be created on demand, or when a component should not be created |
42 | unnecessarily for performance reasons. |
43 | |
44 | \note Loader3D works the same way as \l Loader. The difference between the |
45 | two is that \l Loader provides a way to dynamically load objects that inherit |
46 | \l Item, whereas Loader3D provides a way to load objects that inherit \l Object3D |
47 | and is part of a 3D scene. |
48 | */ |
49 | |
50 | QQuick3DLoader::QQuick3DLoader(QQuick3DNode *parent) |
51 | : QQuick3DNode(parent) |
52 | , m_item(nullptr) |
53 | , m_object(nullptr) |
54 | , m_itemContext(nullptr) |
55 | , m_incubator(nullptr) |
56 | , m_active(true) |
57 | , m_loadingFromSource(false) |
58 | , m_asynchronous(false) |
59 | { |
60 | } |
61 | |
62 | QQuick3DLoader::~QQuick3DLoader() |
63 | { |
64 | delete m_itemContext; |
65 | m_itemContext = nullptr; |
66 | delete m_incubator; |
67 | m_incubator = nullptr; |
68 | disposeInitialPropertyValues(); |
69 | clear(); |
70 | } |
71 | |
72 | /*! |
73 | \qmlproperty bool QtQuick3D::Loader3D::active |
74 | This property is \c true if the Loader3D is currently active. |
75 | The default value for this property is \c true. |
76 | |
77 | If the Loader3D is inactive, changing the \l source or \l sourceComponent |
78 | will not cause the item to be instantiated until the Loader3D is made active. |
79 | |
80 | Setting the value to inactive will cause any \l item loaded by the loader |
81 | to be released, but will not affect the \l source or \l sourceComponent. |
82 | |
83 | The \l status of an inactive loader is always \c Null. |
84 | |
85 | \sa source, sourceComponent |
86 | */ |
87 | |
88 | bool QQuick3DLoader::active() const |
89 | { |
90 | return m_active; |
91 | } |
92 | |
93 | void QQuick3DLoader::setActive(bool newVal) |
94 | { |
95 | if (m_active == newVal) |
96 | return; |
97 | |
98 | m_active = newVal; |
99 | if (newVal) { |
100 | if (m_loadingFromSource) { |
101 | loadFromSource(); |
102 | } else { |
103 | loadFromSourceComponent(); |
104 | } |
105 | } else { |
106 | // cancel any current incubation |
107 | if (m_incubator) { |
108 | m_incubator->clear(); |
109 | delete m_itemContext; |
110 | m_itemContext = nullptr; |
111 | } |
112 | |
113 | // Prevent any bindings from running while waiting for deletion. Without |
114 | // this we may get transient errors from use of 'parent', for example. |
115 | QQmlContext *context = qmlContext(m_object); |
116 | if (context) |
117 | QQmlContextData::get(context)->clearContextRecursively(); |
118 | |
119 | if (m_item) { |
120 | // We can't delete immediately because our item may have triggered |
121 | // the Loader to load a different item. |
122 | m_item->setParentItem(nullptr); |
123 | m_item->setVisible(false); |
124 | m_item = nullptr; |
125 | } |
126 | if (m_object) { |
127 | m_object->deleteLater(); |
128 | m_object = nullptr; |
129 | emit itemChanged(); |
130 | } |
131 | emit statusChanged(); |
132 | } |
133 | emit activeChanged(); |
134 | } |
135 | |
136 | void QQuick3DLoader::setSource(QQmlV4Function *args) |
137 | { |
138 | bool ipvError = false; |
139 | args->setReturnValue(QV4::Encode::undefined()); |
140 | QV4::Scope scope(args->v4engine()); |
141 | QV4::ScopedValue ipv(scope, extractInitialPropertyValues(args, error: &ipvError)); |
142 | if (ipvError) |
143 | return; |
144 | |
145 | clear(); |
146 | QUrl sourceUrl = resolveSourceUrl(args); |
147 | if (!ipv->isUndefined()) { |
148 | disposeInitialPropertyValues(); |
149 | m_initialPropertyValues.set(engine: args->v4engine(), value: ipv); |
150 | } |
151 | m_qmlCallingContext.set(engine: scope.engine, obj: scope.engine->qmlContext()); |
152 | |
153 | setSource(sourceUrl, needsClear: false); // already cleared and set ipv above. |
154 | } |
155 | |
156 | /*! |
157 | \qmlproperty url QtQuick3D::Loader3D::source |
158 | This property holds the URL of the QML component to instantiate. |
159 | |
160 | To unload the currently loaded object, set this property to an empty string, |
161 | or set \l sourceComponent to \c undefined. Setting \c source to a |
162 | new URL will also cause the item created by the previous URL to be unloaded. |
163 | |
164 | \sa sourceComponent, status, progress |
165 | */ |
166 | |
167 | QUrl QQuick3DLoader::source() const |
168 | { |
169 | return m_source; |
170 | } |
171 | |
172 | void QQuick3DLoader::setSource(const QUrl &url) |
173 | { |
174 | setSource(sourceUrl: url, needsClear: true); |
175 | } |
176 | |
177 | /*! |
178 | \qmlproperty Component QtQuick3D::Loader3D::sourceComponent |
179 | This property holds the \l{Component} to instantiate. |
180 | |
181 | \qml |
182 | Item { |
183 | Component { |
184 | id: redCube |
185 | Model { |
186 | source: "#Cube" |
187 | materials: DefaultMaterial { |
188 | diffuseColor: "red" |
189 | } |
190 | } |
191 | } |
192 | |
193 | Loader3D { sourceComponent: redCube } |
194 | Loader3D { sourceComponent: redCube; x: 10 } |
195 | } |
196 | \endqml |
197 | |
198 | To unload the currently loaded object, set this property to \c undefined. |
199 | |
200 | \sa source, progress |
201 | */ |
202 | |
203 | /*! |
204 | \qmlmethod object QtQuick3D::Loader3D::setSource(url source, object properties) |
205 | |
206 | Creates an object instance of the given \a source component that will have |
207 | the given \a properties. The \a properties argument is optional. The instance |
208 | will be accessible via the \l item property once loading and instantiation |
209 | is complete. |
210 | |
211 | If the \l active property is \c false at the time when this function is called, |
212 | the given \a source component will not be loaded but the \a source and initial |
213 | \a properties will be cached. When the loader is made \l active, an instance of |
214 | the \a source component will be created with the initial \a properties set. |
215 | |
216 | Setting the initial property values of an instance of a component in this manner |
217 | will \b{not} trigger any associated \l{Behavior}s. |
218 | |
219 | Note that the cached \a properties will be cleared if the \l source or \l sourceComponent |
220 | is changed after calling this function but prior to setting the loader \l active. |
221 | |
222 | \sa source, active |
223 | */ |
224 | |
225 | QQmlComponent *QQuick3DLoader::sourceComponent() const |
226 | { |
227 | return m_component; |
228 | } |
229 | |
230 | void QQuick3DLoader::setSourceComponent(QQmlComponent *comp) |
231 | { |
232 | if (comp == m_component) |
233 | return; |
234 | |
235 | clear(); |
236 | |
237 | m_component.setObject(obj: comp, parent: this); |
238 | m_loadingFromSource = false; |
239 | |
240 | if (m_active) |
241 | loadFromSourceComponent(); |
242 | else |
243 | emit sourceComponentChanged(); |
244 | } |
245 | |
246 | void QQuick3DLoader::resetSourceComponent() |
247 | { |
248 | setSourceComponent(nullptr); |
249 | } |
250 | |
251 | /*! |
252 | \qmlproperty enumeration QtQuick3D::Loader3D::status |
253 | \readonly |
254 | |
255 | This property holds the status of QML loading. It can be one of: |
256 | |
257 | \value Loader3D.Null The loader is inactive or no QML source has been set. |
258 | \value Loader3D.Ready The QML source has been loaded. |
259 | \value Loader3D.Loading The QML source is currently being loaded. |
260 | \value Loader3D.Error An error occurred while loading the QML source. |
261 | |
262 | Use this status to provide an update or respond to the status change in some way. |
263 | For example, you could: |
264 | |
265 | \list |
266 | \li Trigger a state change: |
267 | \qml |
268 | State { name: 'loaded'; when: loader.status == Loader3D.Ready } |
269 | \endqml |
270 | |
271 | \li Implement an \c onStatusChanged signal handler: |
272 | \qml |
273 | Loader3D { |
274 | id: loader |
275 | onStatusChanged: if (loader.status == Loader3D.Ready) console.log('Loaded') |
276 | } |
277 | \endqml |
278 | |
279 | \li Bind to the status value: |
280 | \qml |
281 | Text { text: loader.status == Loader3D.Ready ? 'Loaded' : 'Not loaded' } |
282 | \endqml |
283 | \endlist |
284 | |
285 | Note that if the source is a local file, the status will initially be Ready (or Error). While |
286 | there will be no onStatusChanged signal in that case, the onLoaded will still be invoked. |
287 | |
288 | \sa progress |
289 | */ |
290 | |
291 | QQuick3DLoader::Status QQuick3DLoader::status() const |
292 | { |
293 | if (!m_active) |
294 | return Null; |
295 | |
296 | if (m_component) { |
297 | switch (m_component->status()) { |
298 | case QQmlComponent::Loading: |
299 | return Loading; |
300 | case QQmlComponent::Error: |
301 | return Error; |
302 | case QQmlComponent::Null: |
303 | return Null; |
304 | default: |
305 | break; |
306 | } |
307 | } |
308 | |
309 | if (m_incubator) { |
310 | switch (m_incubator->status()) { |
311 | case QQmlIncubator::Loading: |
312 | return Loading; |
313 | case QQmlIncubator::Error: |
314 | return Error; |
315 | default: |
316 | break; |
317 | } |
318 | } |
319 | |
320 | if (m_object) |
321 | return Ready; |
322 | |
323 | return m_source.isEmpty() ? Null : Error; |
324 | } |
325 | |
326 | /*! |
327 | \qmlsignal QtQuick3D::Loader3D::loaded() |
328 | |
329 | This signal is emitted when the \l status becomes \c Loader3D.Ready, or on successful |
330 | initial load. |
331 | |
332 | The corresponding handler is \c onLoaded. |
333 | */ |
334 | |
335 | |
336 | /*! |
337 | \qmlproperty real QtQuick3D::Loader3D::progress |
338 | \readonly |
339 | |
340 | This property holds the progress of loading QML data from the network, from |
341 | 0.0 (nothing loaded) to 1.0 (finished). Most QML files are quite small, so |
342 | this value will rapidly change from 0 to 1. |
343 | |
344 | \sa status |
345 | */ |
346 | |
347 | qreal QQuick3DLoader::progress() const |
348 | { |
349 | |
350 | if (m_object) |
351 | return 1.0; |
352 | |
353 | if (m_component) |
354 | return m_component->progress(); |
355 | |
356 | return 0.0; |
357 | } |
358 | |
359 | /*! |
360 | \qmlproperty bool QtQuick3D::Loader3D::asynchronous |
361 | |
362 | This property holds whether the component will be instantiated asynchronously. |
363 | By default it is \c false. |
364 | |
365 | When used in conjunction with the \l source property, loading and compilation |
366 | will also be performed in a background thread. |
367 | |
368 | Loading asynchronously creates the objects declared by the component |
369 | across multiple frames, and reduces the |
370 | likelihood of glitches in animation. When loading asynchronously the status |
371 | will change to Loader3D.Loading. Once the entire component has been created, the |
372 | \l item will be available and the status will change to Loader.Ready. |
373 | |
374 | Changing the value of this property to \c false while an asynchronous load is in |
375 | progress will force immediate, synchronous completion. This allows beginning an |
376 | asynchronous load and then forcing completion if the Loader3D content must be |
377 | accessed before the asynchronous load has completed. |
378 | |
379 | To avoid seeing the items loading progressively set \c visible appropriately, e.g. |
380 | |
381 | \code |
382 | Loader3D { |
383 | source: "mycomponent.qml" |
384 | asynchronous: true |
385 | visible: status == Loader3D.Ready |
386 | } |
387 | \endcode |
388 | |
389 | Note that this property affects object instantiation only; it is unrelated to |
390 | loading a component asynchronously via a network. |
391 | */ |
392 | |
393 | bool QQuick3DLoader::asynchronous() const |
394 | { |
395 | return m_asynchronous; |
396 | } |
397 | |
398 | void QQuick3DLoader::setAsynchronous(bool a) |
399 | { |
400 | if (m_asynchronous == a) |
401 | return; |
402 | |
403 | m_asynchronous = a; |
404 | |
405 | if (!m_asynchronous && isComponentComplete() && m_active) { |
406 | if (m_loadingFromSource && m_component && m_component->isLoading()) { |
407 | // Force a synchronous component load |
408 | QUrl currentSource = m_source; |
409 | clear(); |
410 | m_source = currentSource; |
411 | loadFromSource(); |
412 | } else if (m_incubator && m_incubator->isLoading()) { |
413 | m_incubator->forceCompletion(); |
414 | } |
415 | } |
416 | |
417 | emit asynchronousChanged(); |
418 | } |
419 | |
420 | /*! |
421 | \qmlproperty object QtQuick3D::Loader3D::item |
422 | \readonly |
423 | This property holds the top-level object that is currently loaded. |
424 | */ |
425 | QObject *QQuick3DLoader::item() const |
426 | { |
427 | return m_object; |
428 | } |
429 | |
430 | void QQuick3DLoader::componentComplete() |
431 | { |
432 | QQuick3DNode::componentComplete(); |
433 | if (active()) { |
434 | if (m_loadingFromSource) |
435 | createComponent(); |
436 | load(); |
437 | } |
438 | } |
439 | |
440 | void QQuick3DLoader::sourceLoaded() |
441 | { |
442 | if (!m_component || !m_component->errors().isEmpty()) { |
443 | if (m_component) |
444 | QQmlEnginePrivate::warning(qmlEngine(this), m_component->errors()); |
445 | if (m_loadingFromSource) |
446 | emit sourceChanged(); |
447 | else |
448 | emit sourceComponentChanged(); |
449 | emit statusChanged(); |
450 | emit progressChanged(); |
451 | emit itemChanged(); //Like clearing source, emit itemChanged even if previous item was also null |
452 | disposeInitialPropertyValues(); // cleanup |
453 | return; |
454 | } |
455 | |
456 | QQmlContext *creationContext = m_component->creationContext(); |
457 | if (!creationContext) creationContext = qmlContext(this); |
458 | m_itemContext = new QQmlContext(creationContext); |
459 | m_itemContext->setContextObject(this); |
460 | |
461 | delete m_incubator; |
462 | m_incubator = new QQuick3DLoaderIncubator(this, m_asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); |
463 | |
464 | m_component->create(*m_incubator, context: m_itemContext); |
465 | |
466 | if (m_incubator && m_incubator->status() == QQmlIncubator::Loading) |
467 | emit statusChanged(); |
468 | } |
469 | |
470 | void QQuick3DLoader::setSource(const QUrl &sourceUrl, bool needsClear) |
471 | { |
472 | if (m_source == sourceUrl) |
473 | return; |
474 | |
475 | if (needsClear) |
476 | clear(); |
477 | |
478 | m_source = sourceUrl; |
479 | m_loadingFromSource = true; |
480 | |
481 | if (m_active) |
482 | loadFromSource(); |
483 | else |
484 | emit sourceChanged(); |
485 | } |
486 | |
487 | void QQuick3DLoader::loadFromSource() |
488 | { |
489 | if (m_source.isEmpty()) { |
490 | emit sourceChanged(); |
491 | emit statusChanged(); |
492 | emit progressChanged(); |
493 | emit itemChanged(); |
494 | return; |
495 | } |
496 | |
497 | if (isComponentComplete()) { |
498 | if (!m_component) |
499 | createComponent(); |
500 | load(); |
501 | } |
502 | } |
503 | |
504 | void QQuick3DLoader::loadFromSourceComponent() |
505 | { |
506 | if (!m_component) { |
507 | emit sourceComponentChanged(); |
508 | emit statusChanged(); |
509 | emit progressChanged(); |
510 | emit itemChanged(); |
511 | return; |
512 | } |
513 | |
514 | if (isComponentComplete()) |
515 | load(); |
516 | } |
517 | |
518 | void QQuick3DLoader::clear() |
519 | { |
520 | disposeInitialPropertyValues(); |
521 | |
522 | if (m_incubator) |
523 | m_incubator->clear(); |
524 | |
525 | delete m_itemContext; |
526 | m_itemContext = nullptr; |
527 | |
528 | // Prevent any bindings from running while waiting for deletion. Without |
529 | // this we may get transient errors from use of 'parent', for example. |
530 | QQmlContext *context = qmlContext(m_object); |
531 | if (context) |
532 | QQmlContextData::get(context)->clearContextRecursively(); |
533 | |
534 | if (m_loadingFromSource && m_component) { |
535 | // disconnect since we deleteLater |
536 | QObject::disconnect(sender: m_component, SIGNAL(statusChanged(QQmlComponent::Status)), |
537 | receiver: this, SLOT(sourceLoaded())); |
538 | QObject::disconnect(sender: m_component, SIGNAL(progressChanged(qreal)), |
539 | receiver: this, SIGNAL(progressChanged())); |
540 | m_component->deleteLater(); |
541 | m_component.setObject(obj: nullptr, parent: this); |
542 | } else if (m_component) { |
543 | m_component.setObject(obj: nullptr, parent: this); |
544 | } |
545 | m_source = QUrl(); |
546 | |
547 | if (m_item) { |
548 | // We can't delete immediately because our item may have triggered |
549 | // the Loader to load a different item. |
550 | m_item->setParentItem(nullptr); |
551 | m_item->setVisible(false); |
552 | m_item = nullptr; |
553 | } |
554 | if (m_object) { |
555 | m_object->deleteLater(); |
556 | m_object = nullptr; |
557 | } |
558 | } |
559 | |
560 | void QQuick3DLoader::load() |
561 | { |
562 | |
563 | if (!isComponentComplete() || !m_component) |
564 | return; |
565 | |
566 | if (!m_component->isLoading()) { |
567 | sourceLoaded(); |
568 | } else { |
569 | QObject::connect(sender: m_component, SIGNAL(statusChanged(QQmlComponent::Status)), |
570 | receiver: this, SLOT(sourceLoaded())); |
571 | QObject::connect(sender: m_component, SIGNAL(progressChanged(qreal)), |
572 | receiver: this, SIGNAL(progressChanged())); |
573 | emit statusChanged(); |
574 | emit progressChanged(); |
575 | if (m_loadingFromSource) |
576 | emit sourceChanged(); |
577 | else |
578 | emit sourceComponentChanged(); |
579 | emit itemChanged(); |
580 | } |
581 | } |
582 | |
583 | void QQuick3DLoader::incubatorStateChanged(QQmlIncubator::Status status) |
584 | { |
585 | if (status == QQmlIncubator::Loading || status == QQmlIncubator::Null) |
586 | return; |
587 | |
588 | if (status == QQmlIncubator::Ready) { |
589 | m_object = m_incubator->object(); |
590 | m_item = qmlobject_cast<QQuick3DNode*>(object: m_object); |
591 | emit itemChanged(); |
592 | m_incubator->clear(); |
593 | } else if (status == QQmlIncubator::Error) { |
594 | if (!m_incubator->errors().isEmpty()) |
595 | QQmlEnginePrivate::warning(qmlEngine(this), m_incubator->errors()); |
596 | delete m_itemContext; |
597 | m_itemContext = nullptr; |
598 | delete m_incubator->object(); |
599 | m_source = QUrl(); |
600 | emit itemChanged(); |
601 | } |
602 | if (m_loadingFromSource) |
603 | emit sourceChanged(); |
604 | else |
605 | emit sourceComponentChanged(); |
606 | emit statusChanged(); |
607 | emit progressChanged(); |
608 | if (status == QQmlIncubator::Ready) |
609 | emit loaded(); |
610 | disposeInitialPropertyValues(); // cleanup |
611 | } |
612 | |
613 | void QQuick3DLoader::setInitialState(QObject *obj) |
614 | { |
615 | QQuick3DObject *item = qmlobject_cast<QQuick3DObject*>(object: obj); |
616 | if (item) { |
617 | item->setParentItem(this); |
618 | } |
619 | if (obj) { |
620 | QQml_setParent_noEvent(object: m_itemContext, parent: obj); |
621 | QQml_setParent_noEvent(object: obj, parent: this); |
622 | m_itemContext = nullptr; |
623 | } |
624 | |
625 | if (m_initialPropertyValues.isUndefined()) |
626 | return; |
627 | |
628 | QQmlComponentPrivate *d = QQmlComponentPrivate::get(c: m_component); |
629 | Q_ASSERT(d && d->engine); |
630 | QV4::ExecutionEngine *v4 = d->engine->handle(); |
631 | Q_ASSERT(v4); |
632 | QV4::Scope scope(v4); |
633 | QV4::ScopedValue ipv(scope, m_initialPropertyValues.value()); |
634 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, m_qmlCallingContext.value()); |
635 | d->initializeObjectWithInitialProperties(qmlContext, valuemap: ipv, toCreate: obj, requiredProperties: QQmlIncubatorPrivate::get(incubator: m_incubator)->requiredProperties()); |
636 | } |
637 | |
638 | void QQuick3DLoader::disposeInitialPropertyValues() |
639 | { |
640 | |
641 | } |
642 | |
643 | QUrl QQuick3DLoader::resolveSourceUrl(QQmlV4Function *args) |
644 | { |
645 | QV4::Scope scope(args->v4engine()); |
646 | QV4::ScopedValue v(scope, (*args)[0]); |
647 | QString arg = v->toQString(); |
648 | if (arg.isEmpty()) |
649 | return QUrl(); |
650 | |
651 | auto context = scope.engine->callingQmlContext(); |
652 | Q_ASSERT(!context.isNull()); |
653 | return context->resolvedUrl(QUrl(arg)); |
654 | } |
655 | |
656 | QV4::ReturnedValue QQuick3DLoader::(QQmlV4Function *args, bool *error) |
657 | { |
658 | QV4::Scope scope(args->v4engine()); |
659 | QV4::ScopedValue valuemap(scope, QV4::Encode::undefined()); |
660 | if (args->length() >= 2) { |
661 | QV4::ScopedValue v(scope, (*args)[1]); |
662 | if (!v->isObject() || v->as<QV4::ArrayObject>()) { |
663 | *error = true; |
664 | qmlWarning(me: this) << QQuick3DLoader::tr(s: "setSource: value is not an object" ); |
665 | } else { |
666 | *error = false; |
667 | valuemap = v; |
668 | } |
669 | } |
670 | |
671 | return valuemap->asReturnedValue(); |
672 | } |
673 | |
674 | void QQuick3DLoader::createComponent() |
675 | { |
676 | const QQmlComponent::CompilationMode mode = m_asynchronous |
677 | ? QQmlComponent::Asynchronous |
678 | : QQmlComponent::PreferSynchronous; |
679 | QQmlContext *context = qmlContext(this); |
680 | m_component.setObject(obj: new QQmlComponent(context->engine(), |
681 | context->resolvedUrl(m_source), |
682 | mode, |
683 | this), |
684 | parent: this); |
685 | } |
686 | |
687 | QT_END_NAMESPACE |
688 | |
689 | #include "moc_qquick3dloader_p.cpp" |
690 | |