From 52792ce74ca4b00cb6baaa52d885123645596c83 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 15:31:53 -0700 Subject: [PATCH 01/14] unify existing export code --- src/webots/nodes/WbBackground.cpp | 28 ++++----------------- src/webots/nodes/WbCadShape.cpp | 29 +++++---------------- src/webots/nodes/WbImageTexture.cpp | 15 +++-------- src/webots/nodes/WbMesh.cpp | 13 ++-------- src/webots/vrml/WbNode.cpp | 14 +++++++++++ src/webots/vrml/WbNode.hpp | 4 +++ src/webots/vrml/WbUrl.cpp | 39 +++++++---------------------- src/webots/vrml/WbUrl.hpp | 4 +-- 8 files changed, 44 insertions(+), 102 deletions(-) diff --git a/src/webots/nodes/WbBackground.cpp b/src/webots/nodes/WbBackground.cpp index 6b445a25665..d6ff9420954 100644 --- a/src/webots/nodes/WbBackground.cpp +++ b/src/webots/nodes/WbBackground.cpp @@ -691,17 +691,8 @@ void WbBackground::exportNodeFields(WbWriter &writer) const { continue; WbField urlFieldCopy(*findField(gUrlNames(i), true)); - const QString &imagePath = WbUrl::computePath(this, gUrlNames(i), mUrlFields[i]->item(0)); - if (WbUrl::isLocalUrl(imagePath)) - backgroundFileNames[i] = WbUrl::computeLocalAssetUrl(imagePath, writer.isW3d()); - else if (WbUrl::isWeb(imagePath)) - backgroundFileNames[i] = imagePath; - else { - if (writer.isWritingToFile()) - backgroundFileNames[i] = WbUrl::exportResource(this, imagePath, imagePath, writer.relativeTexturesPath(), writer); - else - backgroundFileNames[i] = WbUrl::expressRelativeToWorld(imagePath); - } + const QString &resolvedURL = WbUrl::computePath(this, gUrlNames(i), mUrlFields[i], 0); + backgroundFileNames[i] = exportResource(mUrlFields[i]->item(0), resolvedURL, writer.relativeTexturesPath(), writer); } QString irradianceFileNames[6]; @@ -709,18 +700,9 @@ void WbBackground::exportNodeFields(WbWriter &writer) const { if (mIrradianceUrlFields[i]->size() == 0) continue; - const QString &irradiancePath = WbUrl::computePath(this, gIrradianceUrlNames(i), mIrradianceUrlFields[i]->item(0)); - if (WbUrl::isLocalUrl(irradiancePath)) - irradianceFileNames[i] = WbUrl::computeLocalAssetUrl(irradiancePath, writer.isW3d()); - else if (WbUrl::isWeb(irradiancePath)) - irradianceFileNames[i] = irradiancePath; - else { - if (writer.isWritingToFile()) - irradianceFileNames[i] = - WbUrl::exportResource(this, irradiancePath, irradiancePath, writer.relativeTexturesPath(), writer); - else - irradianceFileNames[i] = WbUrl::expressRelativeToWorld(irradiancePath); - } + const QString &resolvedURL = WbUrl::computePath(this, gIrradianceUrlNames(i), mIrradianceUrlFields[i], 0); + irradianceFileNames[i] = exportResource(mIrradianceUrlFields[i]->item(0), resolvedURL, + writer.relativeTexturesPath(), writer); } writer << " "; diff --git a/src/webots/nodes/WbCadShape.cpp b/src/webots/nodes/WbCadShape.cpp index aad3201001d..4026549e2c7 100644 --- a/src/webots/nodes/WbCadShape.cpp +++ b/src/webots/nodes/WbCadShape.cpp @@ -571,43 +571,26 @@ void WbCadShape::exportNodeFields(WbWriter &writer) const { if (!(writer.isW3d() || writer.isProto())) return; + WbBaseNode::exportNodeFields(writer); + if (mUrl->size() == 0) return; - WbBaseNode::exportNodeFields(writer); - // export model WbField urlFieldCopy(*findField("url", true)); for (int i = 0; i < mUrl->size(); ++i) { - const QString &completeUrl = WbUrl::computePath(this, "url", mUrl, i); + const QString &resolvedURL = WbUrl::computePath(this, "url", mUrl, i); WbMFString *urlFieldValue = dynamic_cast(urlFieldCopy.value()); - if (WbUrl::isLocalUrl(completeUrl)) - urlFieldValue->setItem(i, WbUrl::computeLocalAssetUrl(completeUrl, writer.isW3d())); - else if (WbUrl::isWeb(completeUrl)) - urlFieldValue->setItem(i, completeUrl); - else { - urlFieldValue->setItem( - i, writer.isWritingToFile() ? WbUrl::exportMesh(this, mUrl, i, writer) : WbUrl::expressRelativeToWorld(completeUrl)); - } + urlFieldValue->setItem(i, exportResource(mUrl->item(i), resolvedURL, writer.relativeMeshesPath(), writer)); } // export materials if (writer.isW3d()) { // only needs to be included in the w3d, when converting to base node it shouldn't be included - const QString &parentUrl = WbUrl::computePath(this, "url", mUrl->item(0)); + const QString &parentUrl = WbUrl::computePath(this, "url", mUrl, 0); for (const QString &material : objMaterialList(parentUrl)) { QString materialUrl = WbUrl::combinePaths(material, parentUrl); WbMFString *urlFieldValue = dynamic_cast(urlFieldCopy.value()); - if (WbUrl::isLocalUrl(materialUrl)) - urlFieldValue->addItem(WbUrl::computeLocalAssetUrl(materialUrl, writer.isW3d())); - else if (WbUrl::isWeb(materialUrl)) - urlFieldValue->addItem(materialUrl); - else { - if (writer.isWritingToFile()) - urlFieldValue->addItem( - WbUrl::exportResource(this, materialUrl, materialUrl, writer.relativeMeshesPath(), writer, false)); - else - urlFieldValue->addItem(WbUrl::expressRelativeToWorld(materialUrl)); - } + urlFieldValue->addItem(exportResource(material, materialUrl, writer.relativeMeshesPath(), writer)); } } diff --git a/src/webots/nodes/WbImageTexture.cpp b/src/webots/nodes/WbImageTexture.cpp index be09007f51c..6c6fc7fd842 100644 --- a/src/webots/nodes/WbImageTexture.cpp +++ b/src/webots/nodes/WbImageTexture.cpp @@ -531,18 +531,9 @@ void WbImageTexture::exportNodeFields(WbWriter &writer) const { // export to ./textures folder relative to writer path WbField urlFieldCopy(*findField("url", true)); for (int i = 0; i < mUrl->size(); ++i) { - QString completeUrl = WbUrl::computePath(this, "url", mUrl, i); + const QString &resolvedURL = WbUrl::computePath(this, "url", mUrl, i); WbMFString *urlFieldValue = dynamic_cast(urlFieldCopy.value()); - if (WbUrl::isLocalUrl(completeUrl)) - urlFieldValue->setItem(i, WbUrl::computeLocalAssetUrl(completeUrl, writer.isW3d())); - else if (WbUrl::isWeb(completeUrl)) - urlFieldValue->setItem(i, completeUrl); - else { - if (writer.isWritingToFile()) - urlFieldValue->setItem(i, WbUrl::exportTexture(this, mUrl, i, writer)); - else - urlFieldValue->setItem(i, WbUrl::expressRelativeToWorld(completeUrl)); - } + urlFieldValue->setItem(i, exportResource(mUrl->item(i), resolvedURL, writer.relativeTexturesPath(), writer)); } urlFieldCopy.write(writer); @@ -560,7 +551,7 @@ void WbImageTexture::exportShallowNode(const WbWriter &writer) const { // note: the texture of the shallow nodes needs to be exported only if the URL is locally defined but not of type // 'webots://' since this case would be converted to a remote one that targets the current branch if (!WbUrl::isWeb(mUrl->item(0)) && !WbUrl::isLocalUrl(mUrl->item(0)) && !WbWorld::isW3dStreaming()) - WbUrl::exportTexture(this, mUrl, 0, writer); + WbUrl::exportResource(this, mUrl->item(0), WbUrl::computePath(this, "url", mUrl, 0), writer.relativeTexturesPath(), writer); } QStringList WbImageTexture::fieldsToSynchronizeWithW3d() const { diff --git a/src/webots/nodes/WbMesh.cpp b/src/webots/nodes/WbMesh.cpp index f2cc552c65d..ab5971aa786 100644 --- a/src/webots/nodes/WbMesh.cpp +++ b/src/webots/nodes/WbMesh.cpp @@ -409,18 +409,9 @@ void WbMesh::exportNodeFields(WbWriter &writer) const { WbField urlFieldCopy(*findField("url", true)); for (int i = 0; i < mUrl->size(); ++i) { - const QString &completeUrl = WbUrl::computePath(this, "url", mUrl, i); + const QString &resolvedURL = WbUrl::computePath(this, "url", mUrl, i); WbMFString *urlFieldValue = dynamic_cast(urlFieldCopy.value()); - if (WbUrl::isLocalUrl(completeUrl)) - urlFieldValue->setItem(i, WbUrl::computeLocalAssetUrl(completeUrl, writer.isW3d())); - else if (WbUrl::isWeb(completeUrl)) - urlFieldValue->setItem(i, completeUrl); - else { - if (writer.isWritingToFile()) - urlFieldValue->setItem(i, WbUrl::exportMesh(this, mUrl, i, writer)); - else - urlFieldValue->setItem(i, WbUrl::expressRelativeToWorld(completeUrl)); - } + urlFieldValue->setItem(i, exportResource(mUrl->item(i), resolvedURL, writer.relativeMeshesPath(), writer)); } urlFieldCopy.write(writer); diff --git a/src/webots/vrml/WbNode.cpp b/src/webots/vrml/WbNode.cpp index 17f0639efab..5050c80ea2a 100644 --- a/src/webots/vrml/WbNode.cpp +++ b/src/webots/vrml/WbNode.cpp @@ -1244,6 +1244,20 @@ void WbNode::writeExport(WbWriter &writer) const { } } +QString WbNode::exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, + WbWriter &writer) const { + if (WbUrl::isLocalUrl(resolvedURL)) + return WbUrl::computeLocalAssetUrl(resolvedURL, writer.isW3d()); + else if (WbUrl::isWeb(resolvedURL)) + return resolvedURL; + else { + if (writer.isWritingToFile()) + return WbUrl::exportResource(this, rawURL, resolvedURL, relativeResourcePath, writer); + else + return WbUrl::expressRelativeToWorld(resolvedURL); + } +} + bool WbNode::operator==(const WbNode &other) const { if (mModel != other.mModel || isProtoInstance() != other.isProtoInstance() || (mProto && mProto->url() != other.mProto->url()) || mDefName != other.mDefName) diff --git a/src/webots/vrml/WbNode.hpp b/src/webots/vrml/WbNode.hpp index c76e5dee527..42a0e9c6644 100644 --- a/src/webots/vrml/WbNode.hpp +++ b/src/webots/vrml/WbNode.hpp @@ -317,6 +317,10 @@ class WbNode : public QObject { virtual void exportNodeFooter(WbWriter &writer) const; virtual void exportExternalSubProto(WbWriter &writer) const; + // Helper to handle exporting fields with resources which reference external files + QString exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, + WbWriter &writer) const; + // Methods related to URDF export const WbNode *findUrdfLinkRoot() const; // Finds first upper Webots node that is considered as URDF link virtual bool isUrdfRootLink() const; // Determines whether the Webots node is considered as URDF link as well diff --git a/src/webots/vrml/WbUrl.cpp b/src/webots/vrml/WbUrl.cpp index d126f6696e4..3f32a1e9491 100644 --- a/src/webots/vrml/WbUrl.cpp +++ b/src/webots/vrml/WbUrl.cpp @@ -58,17 +58,18 @@ QString WbUrl::missing(const QString &url) { return ""; } -QString WbUrl::computePath(const WbNode *node, const QString &field, const WbMFString *urlField, int index, bool showWarning) { +QString WbUrl::computePath(const WbNode *node, const QString &field, const WbMFString *urlField, int index, bool showWarning, + bool disableCache) { // check if mUrl is empty if (urlField->size() < 1) return ""; // get the URL at specified index const QString &url = urlField->item(index); - return computePath(node, field, url, showWarning); + return computePath(node, field, url, showWarning, disableCache); } -QString WbUrl::computePath(const WbNode *node, const QString &field, const QString &rawUrl, bool showWarning) { +QString WbUrl::computePath(const WbNode *node, const QString &field, const QString &rawUrl, bool showWarning, bool disableCache) { QString url = resolveUrl(rawUrl); // check if the first URL is empty if (url.isEmpty()) { @@ -85,6 +86,7 @@ QString WbUrl::computePath(const WbNode *node, const QString &field, const QStri if (QDir::isRelativePath(url)) { const WbField *f = node->findField(field, true); + assert(f); const WbNode *protoNode = node->containingProto(false); protoNode = WbVrmlNodeUtilities::findFieldProtoScope(f, protoNode); @@ -94,7 +96,7 @@ QString WbUrl::computePath(const WbNode *node, const QString &field, const QStri // note: derived PROTO are a special case because instances of the intermediary ancestors from which it is defined don't // persist after the build process, hence why we keep track of the scope while building the node itself if (protoNode->proto()->isDerived()) { - if (WbFileUtil::isLocatedInDirectory(f->scope(), WbStandardPaths::cachedAssetsPath())) + if (!disableCache && WbFileUtil::isLocatedInDirectory(f->scope(), WbStandardPaths::cachedAssetsPath())) parentUrl = WbNetwork::instance()->getUrlFromEphemeralCache(f->scope()); else parentUrl = f->scope(); @@ -132,7 +134,7 @@ QString WbUrl::resolveUrl(const QString &rawUrl) { } QString WbUrl::exportResource(const WbNode *node, const QString &url, const QString &sourcePath, - const QString &relativeResourcePath, const WbWriter &writer, const bool isTexture) { + const QString &relativeResourcePath, const WbWriter &writer) { const QFileInfo urlFileInfo(url); const QString fileName = urlFileInfo.fileName(); const QString expectedRelativePath = relativeResourcePath + fileName; @@ -155,11 +157,7 @@ QString WbUrl::exportResource(const WbNode *node, const QString &url, const QStr const QString extension = urlFileInfo.suffix(); for (int i = 1; i < 100; ++i) { // number of trials before failure - QString newRelativePath; - if (isTexture) - newRelativePath = writer.relativeTexturesPath() + baseName + '.' + QString::number(i) + '.' + extension; - else - newRelativePath = writer.relativeMeshesPath() + baseName + '.' + QString::number(i) + '.' + extension; + QString newRelativePath = relativeResourcePath + baseName + '.' + QString::number(i) + '.' + extension; const QString newAbsolutePath = writer.path() + "/" + newRelativePath; if (QFileInfo(newAbsolutePath).exists()) { @@ -170,11 +168,8 @@ QString WbUrl::exportResource(const WbNode *node, const QString &url, const QStr return newRelativePath; } } - if (isTexture) - node->warn(QObject::tr("Failure exporting texture, too many textures share the same name: %1.").arg(url)); - else - node->warn(QObject::tr("Failure exporting mesh, too many meshes share the same name: %1.").arg(url)); + node->warn(QObject::tr("Failure exporting resource, too many resources share the same name: %1.").arg(url)); return ""; } } else { // simple case @@ -183,22 +178,6 @@ QString WbUrl::exportResource(const WbNode *node, const QString &url, const QStr } } -QString WbUrl::exportTexture(const WbNode *node, const WbMFString *urlField, int index, const WbWriter &writer) { - // in addition to writing the node, we want to ensure that the texture file exists - // at the expected location. If not, we should copy it, possibly creating the expected - // directory structure. - return exportResource(node, QDir::fromNativeSeparators(urlField->item(index)), computePath(node, "url", urlField, index), - writer.relativeTexturesPath(), writer); -} - -QString WbUrl::exportMesh(const WbNode *node, const WbMFString *urlField, int index, const WbWriter &writer) { - // in addition to writing the node, we want to ensure that the mesh file exists - // at the expected location. If not, we should copy it, possibly creating the expected - // directory structure. - return exportResource(node, QDir::fromNativeSeparators(urlField->item(index)), computePath(node, "url", urlField, index), - writer.relativeMeshesPath(), writer, false); -} - bool WbUrl::isWeb(const QString &url) { return url.startsWith("https://") || url.startsWith("http://"); } diff --git a/src/webots/vrml/WbUrl.hpp b/src/webots/vrml/WbUrl.hpp index a52cf10a501..48fa8753b82 100644 --- a/src/webots/vrml/WbUrl.hpp +++ b/src/webots/vrml/WbUrl.hpp @@ -30,9 +30,7 @@ namespace WbUrl { QString combinePaths(const QString &rawUrl, const QString &rawParentUrl); QString exportResource(const WbNode *node, const QString &url, const QString &sourcePath, const QString &relativeResourcePath, - const WbWriter &writer, const bool isTexture = true); - QString exportTexture(const WbNode *node, const WbMFString *urlField, int index, const WbWriter &writer); - QString exportMesh(const WbNode *node, const WbMFString *urlField, int index, const WbWriter &writer); + const WbWriter &writer); QString missing(const QString &url); const QString &missingTexture(); From 884ef677975dd507a0a58f11dd4dfa50689b34d6 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 15:50:25 -0700 Subject: [PATCH 02/14] add simplified normal use case helper --- src/webots/nodes/WbImageTexture.cpp | 10 +-------- src/webots/nodes/WbMesh.cpp | 12 +--------- src/webots/vrml/WbNode.cpp | 34 +++++++++++++++++++++++++++++ src/webots/vrml/WbNode.hpp | 5 +++++ 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/webots/nodes/WbImageTexture.cpp b/src/webots/nodes/WbImageTexture.cpp index 6c6fc7fd842..159029e5594 100644 --- a/src/webots/nodes/WbImageTexture.cpp +++ b/src/webots/nodes/WbImageTexture.cpp @@ -528,15 +528,7 @@ bool WbImageTexture::exportNodeHeader(WbWriter &writer) const { void WbImageTexture::exportNodeFields(WbWriter &writer) const { WbBaseNode::exportNodeFields(writer); - // export to ./textures folder relative to writer path - WbField urlFieldCopy(*findField("url", true)); - for (int i = 0; i < mUrl->size(); ++i) { - const QString &resolvedURL = WbUrl::computePath(this, "url", mUrl, i); - WbMFString *urlFieldValue = dynamic_cast(urlFieldCopy.value()); - urlFieldValue->setItem(i, exportResource(mUrl->item(i), resolvedURL, writer.relativeTexturesPath(), writer)); - } - - urlFieldCopy.write(writer); + exportMFResourceField("url", mUrl, writer.relativeTexturesPath(), writer); if (writer.isW3d()) { if (!mRole.isEmpty()) diff --git a/src/webots/nodes/WbMesh.cpp b/src/webots/nodes/WbMesh.cpp index ab5971aa786..6927c8c9854 100644 --- a/src/webots/nodes/WbMesh.cpp +++ b/src/webots/nodes/WbMesh.cpp @@ -404,17 +404,7 @@ void WbMesh::exportNodeFields(WbWriter &writer) const { if (!(writer.isW3d() || writer.isProto())) return; - if (mUrl->size() == 0) - return; - - WbField urlFieldCopy(*findField("url", true)); - for (int i = 0; i < mUrl->size(); ++i) { - const QString &resolvedURL = WbUrl::computePath(this, "url", mUrl, i); - WbMFString *urlFieldValue = dynamic_cast(urlFieldCopy.value()); - urlFieldValue->setItem(i, exportResource(mUrl->item(i), resolvedURL, writer.relativeMeshesPath(), writer)); - } - - urlFieldCopy.write(writer); + exportMFResourceField("url", mUrl, writer.relativeMeshesPath(), writer); WbGeometry::exportNodeFields(writer); } diff --git a/src/webots/vrml/WbNode.cpp b/src/webots/vrml/WbNode.cpp index 5050c80ea2a..573f02df35c 100644 --- a/src/webots/vrml/WbNode.cpp +++ b/src/webots/vrml/WbNode.cpp @@ -1258,6 +1258,40 @@ QString WbNode::exportResource(const QString &rawURL, const QString &resolvedURL } } +void WbNode::exportMFResourceField(const QString &fieldName, const WbMFString* value, const QString &relativeResourcePath, WbWriter &writer) const { + if (value->size() == 0) return; + + const WbField *originalField = findField(fieldName, true); + assert(originalField && originalField->type() == WB_MF_STRING); + + WbField copiedField(*originalField); + WbMFString *newValue = dynamic_cast(copiedField.value()); + + for (int i = 0; i < value->size(); ++i) { + const QString &rawURL = value->item(i); + const QString &resolvedURL = WbUrl::computePath(this, fieldName, rawURL); + newValue->setItem(i, exportResource(rawURL, resolvedURL, relativeResourcePath, writer)); + } + + copiedField.write(writer); +} + +void WbNode::exportSFResourceField(const QString &fieldName, const WbSFString* value, const QString &relativeResourcePath, WbWriter &writer) const { + const QString &rawURL = value->value(); + if (rawURL.isEmpty()) return; + + const WbField *originalField = findField(fieldName, true); + assert(originalField && originalField->type() == WB_SF_STRING); + + WbField copiedField(*originalField); + WbSFString *newValue = dynamic_cast(copiedField.value()); + + const QString &resolvedURL = WbUrl::computePath(this, fieldName, rawURL); + newValue->setValue(exportResource(rawURL, resolvedURL, relativeResourcePath, writer)); + + copiedField.write(writer); +} + bool WbNode::operator==(const WbNode &other) const { if (mModel != other.mModel || isProtoInstance() != other.isProtoInstance() || (mProto && mProto->url() != other.mProto->url()) || mDefName != other.mDefName) diff --git a/src/webots/vrml/WbNode.hpp b/src/webots/vrml/WbNode.hpp index 42a0e9c6644..79c98655616 100644 --- a/src/webots/vrml/WbNode.hpp +++ b/src/webots/vrml/WbNode.hpp @@ -320,6 +320,11 @@ class WbNode : public QObject { // Helper to handle exporting fields with resources which reference external files QString exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, WbWriter &writer) const; + // Wrappers for the most common use case (simply exporting a field with no additional processing) + void exportMFResourceField(const QString &fieldName, const WbMFString* value, const QString &relativeResourcePath, + WbWriter &writer) const; + void exportSFResourceField(const QString &fieldName, const WbSFString* value, const QString &relativeResourcePath, + WbWriter &writer) const; // Methods related to URDF export const WbNode *findUrdfLinkRoot() const; // Finds first upper Webots node that is considered as URDF link From 4813f9703ac5732b0bfb77cd66230e561d96cae2 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 16:02:12 -0700 Subject: [PATCH 03/14] cleanup existing export methods --- src/webots/nodes/WbBackground.cpp | 18 +++++++++++------- src/webots/nodes/WbCadShape.cpp | 8 +++++--- src/webots/nodes/WbMesh.cpp | 5 +---- src/webots/vrml/WbNode.cpp | 22 +++++++++++++++++----- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/webots/nodes/WbBackground.cpp b/src/webots/nodes/WbBackground.cpp index d6ff9420954..4b106b09b40 100644 --- a/src/webots/nodes/WbBackground.cpp +++ b/src/webots/nodes/WbBackground.cpp @@ -679,10 +679,7 @@ WbRgb WbBackground::skyColor() const { } void WbBackground::exportNodeFields(WbWriter &writer) const { - if (writer.isWebots()) { - WbBaseNode::exportNodeFields(writer); - return; - } + WbBaseNode::exportNodeFields(writer); if (writer.isW3d()) { QString backgroundFileNames[6]; @@ -690,7 +687,6 @@ void WbBackground::exportNodeFields(WbWriter &writer) const { if (mUrlFields[i]->size() == 0) continue; - WbField urlFieldCopy(*findField(gUrlNames(i), true)); const QString &resolvedURL = WbUrl::computePath(this, gUrlNames(i), mUrlFields[i], 0); backgroundFileNames[i] = exportResource(mUrlFields[i]->item(0), resolvedURL, writer.relativeTexturesPath(), writer); } @@ -712,7 +708,15 @@ void WbBackground::exportNodeFields(WbWriter &writer) const { if (!irradianceFileNames[i].isEmpty()) writer << gIrradianceUrlNames(i) << "='\"" << irradianceFileNames[i] << "\"' "; } + } else if (writer.isProto()) { + for (int i = 0; i < 6; ++i) { + exportMFResourceField(gUrlNames(i), mUrlFields[i], writer.relativeTexturesPath(), writer); + exportMFResourceField(gIrradianceUrlNames(i), mIrradianceUrlFields[i], writer.relativeTexturesPath(), writer); + } + } else { + for (int i = 0; i < 6; ++i) { + findField(gUrlNames(i), true)->write(writer); + findField(gIrradianceUrlNames(i), true)->write(writer); + } } - - WbBaseNode::exportNodeFields(writer); } diff --git a/src/webots/nodes/WbCadShape.cpp b/src/webots/nodes/WbCadShape.cpp index 4026549e2c7..f90d66d12fe 100644 --- a/src/webots/nodes/WbCadShape.cpp +++ b/src/webots/nodes/WbCadShape.cpp @@ -568,14 +568,16 @@ const WbVector3 WbCadShape::absoluteScale() const { } void WbCadShape::exportNodeFields(WbWriter &writer) const { - if (!(writer.isW3d() || writer.isProto())) - return; - WbBaseNode::exportNodeFields(writer); if (mUrl->size() == 0) return; + if (!(writer.isW3d() || writer.isProto())) { + findField("url", true)->write(writer); + return; + } + // export model WbField urlFieldCopy(*findField("url", true)); for (int i = 0; i < mUrl->size(); ++i) { diff --git a/src/webots/nodes/WbMesh.cpp b/src/webots/nodes/WbMesh.cpp index 6927c8c9854..fc659fea979 100644 --- a/src/webots/nodes/WbMesh.cpp +++ b/src/webots/nodes/WbMesh.cpp @@ -401,12 +401,9 @@ void WbMesh::updateMaterialIndex() { } void WbMesh::exportNodeFields(WbWriter &writer) const { - if (!(writer.isW3d() || writer.isProto())) - return; + WbGeometry::exportNodeFields(writer); exportMFResourceField("url", mUrl, writer.relativeMeshesPath(), writer); - - WbGeometry::exportNodeFields(writer); } QStringList WbMesh::fieldsToSynchronizeWithW3d() const { diff --git a/src/webots/vrml/WbNode.cpp b/src/webots/vrml/WbNode.cpp index 573f02df35c..d096a378d3b 100644 --- a/src/webots/vrml/WbNode.cpp +++ b/src/webots/vrml/WbNode.cpp @@ -1259,11 +1259,17 @@ QString WbNode::exportResource(const QString &rawURL, const QString &resolvedURL } void WbNode::exportMFResourceField(const QString &fieldName, const WbMFString* value, const QString &relativeResourcePath, WbWriter &writer) const { - if (value->size() == 0) return; - const WbField *originalField = findField(fieldName, true); assert(originalField && originalField->type() == WB_MF_STRING); + // only w3c and proto exports need urls to be resolved + if (!(writer.isW3d() || writer.isProto())) { + originalField->write(writer); + return; + } + + if (value->size() == 0) return; + WbField copiedField(*originalField); WbMFString *newValue = dynamic_cast(copiedField.value()); @@ -1277,12 +1283,18 @@ void WbNode::exportMFResourceField(const QString &fieldName, const WbMFString* v } void WbNode::exportSFResourceField(const QString &fieldName, const WbSFString* value, const QString &relativeResourcePath, WbWriter &writer) const { - const QString &rawURL = value->value(); - if (rawURL.isEmpty()) return; - const WbField *originalField = findField(fieldName, true); assert(originalField && originalField->type() == WB_SF_STRING); + // only w3c and proto exports need urls to be resolved + if (!(writer.isW3d() || writer.isProto())) { + originalField->write(writer); + return; + } + + const QString &rawURL = value->value(); + if (rawURL.isEmpty()) return; + WbField copiedField(*originalField); WbSFString *newValue = dynamic_cast(copiedField.value()); From 3846fbdd52807d23e12df83d23eee1e039d7abf6 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 16:08:50 -0700 Subject: [PATCH 04/14] prevent duplicate exports --- src/webots/nodes/WbBackground.cpp | 9 +++++++++ src/webots/nodes/WbBackground.hpp | 1 + src/webots/nodes/WbCadShape.cpp | 6 ++++++ src/webots/nodes/WbCadShape.hpp | 1 + src/webots/nodes/WbImageTexture.cpp | 6 ++++++ src/webots/nodes/WbImageTexture.hpp | 1 + src/webots/nodes/WbMesh.cpp | 6 ++++++ src/webots/nodes/WbMesh.hpp | 1 + src/webots/vrml/WbNode.cpp | 3 ++- src/webots/vrml/WbNode.hpp | 3 +++ 10 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/webots/nodes/WbBackground.cpp b/src/webots/nodes/WbBackground.cpp index 4b106b09b40..37c2fe79569 100644 --- a/src/webots/nodes/WbBackground.cpp +++ b/src/webots/nodes/WbBackground.cpp @@ -720,3 +720,12 @@ void WbBackground::exportNodeFields(WbWriter &writer) const { } } } + +QStringList WbBackground::customExportedFields() const { + QStringList fields; + for (int i = 0; i < 6; ++i) { + fields << gUrlNames(i); + fields << gIrradianceUrlNames(i); + } + return fields; +} diff --git a/src/webots/nodes/WbBackground.hpp b/src/webots/nodes/WbBackground.hpp index a90c9ad4b1b..c6340596215 100644 --- a/src/webots/nodes/WbBackground.hpp +++ b/src/webots/nodes/WbBackground.hpp @@ -59,6 +59,7 @@ class WbBackground : public WbBaseNode { protected: void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; private: static QList cBackgroundList; diff --git a/src/webots/nodes/WbCadShape.cpp b/src/webots/nodes/WbCadShape.cpp index f90d66d12fe..9b5b4dbd2a3 100644 --- a/src/webots/nodes/WbCadShape.cpp +++ b/src/webots/nodes/WbCadShape.cpp @@ -603,6 +603,12 @@ void WbCadShape::exportNodeFields(WbWriter &writer) const { urlFieldCopy.write(writer); } +QStringList WbCadShape::customExportedFields() const { + QStringList fields; + fields << "url"; + return fields; +} + QString WbCadShape::cadPath() const { return WbUrl::computePath(this, "url", mUrl, 0); } diff --git a/src/webots/nodes/WbCadShape.hpp b/src/webots/nodes/WbCadShape.hpp index fd02124b011..924f4556541 100644 --- a/src/webots/nodes/WbCadShape.hpp +++ b/src/webots/nodes/WbCadShape.hpp @@ -56,6 +56,7 @@ class WbCadShape : public WbBaseNode { protected: void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; WbBoundingSphere *boundingSphere() const override { return mBoundingSphere; } void recomputeBoundingSphere() const; diff --git a/src/webots/nodes/WbImageTexture.cpp b/src/webots/nodes/WbImageTexture.cpp index 159029e5594..026f5da5e3a 100644 --- a/src/webots/nodes/WbImageTexture.cpp +++ b/src/webots/nodes/WbImageTexture.cpp @@ -536,6 +536,12 @@ void WbImageTexture::exportNodeFields(WbWriter &writer) const { } } +QStringList WbImageTexture::customExportedFields() const { + QStringList fields; + fields << "url"; + return fields; +} + void WbImageTexture::exportShallowNode(const WbWriter &writer) const { if (!writer.isW3d() || mUrl->size() == 0) return; diff --git a/src/webots/nodes/WbImageTexture.hpp b/src/webots/nodes/WbImageTexture.hpp index 04bbde70135..b68e81e0073 100644 --- a/src/webots/nodes/WbImageTexture.hpp +++ b/src/webots/nodes/WbImageTexture.hpp @@ -80,6 +80,7 @@ class WbImageTexture : public WbBaseNode { protected: bool exportNodeHeader(WbWriter &writer) const override; void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; private: // user accessible fields diff --git a/src/webots/nodes/WbMesh.cpp b/src/webots/nodes/WbMesh.cpp index fc659fea979..0d6623d5f10 100644 --- a/src/webots/nodes/WbMesh.cpp +++ b/src/webots/nodes/WbMesh.cpp @@ -406,6 +406,12 @@ void WbMesh::exportNodeFields(WbWriter &writer) const { exportMFResourceField("url", mUrl, writer.relativeMeshesPath(), writer); } +QStringList WbMesh::customExportedFields() const { + QStringList fields; + fields << "url"; + return fields; +} + QStringList WbMesh::fieldsToSynchronizeWithW3d() const { QStringList fields; fields << "url" diff --git a/src/webots/nodes/WbMesh.hpp b/src/webots/nodes/WbMesh.hpp index 2de1cdf027b..67acd664b05 100644 --- a/src/webots/nodes/WbMesh.hpp +++ b/src/webots/nodes/WbMesh.hpp @@ -48,6 +48,7 @@ class WbMesh : public WbTriangleMeshGeometry { protected: void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; private: // user accessible fields diff --git a/src/webots/vrml/WbNode.cpp b/src/webots/vrml/WbNode.cpp index d096a378d3b..3987e1a87d3 100644 --- a/src/webots/vrml/WbNode.cpp +++ b/src/webots/vrml/WbNode.cpp @@ -1111,7 +1111,8 @@ void WbNode::exportNodeFields(WbWriter &writer) const { return; foreach (const WbField *f, fields()) { - if (!f->isDeprecated() && ((f->isW3d() || writer.isProto()) && f->singleType() != WB_SF_NODE)) + if (!f->isDeprecated() && ((f->isW3d() || writer.isProto()) && f->singleType() != WB_SF_NODE) && + !customExportedFields().contains(f->name())) f->write(writer); } } diff --git a/src/webots/vrml/WbNode.hpp b/src/webots/vrml/WbNode.hpp index 79c98655616..883d59300c4 100644 --- a/src/webots/vrml/WbNode.hpp +++ b/src/webots/vrml/WbNode.hpp @@ -317,6 +317,9 @@ class WbNode : public QObject { virtual void exportNodeFooter(WbWriter &writer) const; virtual void exportExternalSubProto(WbWriter &writer) const; + // fields which should not be handled by the default exportNodeFields function + virtual QStringList customExportedFields() const { return QStringList(); } + // Helper to handle exporting fields with resources which reference external files QString exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, WbWriter &writer) const; From d22d7c446b1dff5bdfd0266fc01e7abddc1b0a71 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 16:26:57 -0700 Subject: [PATCH 05/14] add export support for resources that don't already have it --- src/webots/nodes/WbCamera.cpp | 12 ++++++++++++ src/webots/nodes/WbCamera.hpp | 2 ++ src/webots/nodes/WbContactProperties.cpp | 14 ++++++++++++++ src/webots/nodes/WbContactProperties.hpp | 4 ++++ src/webots/nodes/WbMotor.cpp | 12 ++++++++++++ src/webots/nodes/WbMotor.hpp | 3 +++ src/webots/nodes/WbSkin.cpp | 12 ++++++++++++ src/webots/nodes/WbSkin.hpp | 4 ++++ src/webots/nodes/WbTrackWheel.cpp | 7 +++---- src/webots/vrml/WbWriter.hpp | 1 + 10 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/webots/nodes/WbCamera.cpp b/src/webots/nodes/WbCamera.cpp index 63133128695..eaeb1384fb0 100644 --- a/src/webots/nodes/WbCamera.cpp +++ b/src/webots/nodes/WbCamera.cpp @@ -1017,6 +1017,18 @@ WbVector3 WbCamera::urdfRotation(const WbMatrix3 &rotationMatrix) const { return eulerRotation; } +void WbCamera::exportNodeFields(WbWriter &writer) const { + WbAbstractCamera::exportNodeFields(writer); + + exportSFResourceField("noiseMaskUrl", mNoiseMaskUrl, writer.relativeMeshesPath(), writer); +} + +QStringList WbCamera::customExportedFields() const { + QStringList fields; + fields << "noiseMaskUrl"; + return fields; +} + ///////////////////// // Update methods // ///////////////////// diff --git a/src/webots/nodes/WbCamera.hpp b/src/webots/nodes/WbCamera.hpp index 456a6c09ca2..b3086d5f32e 100644 --- a/src/webots/nodes/WbCamera.hpp +++ b/src/webots/nodes/WbCamera.hpp @@ -67,6 +67,8 @@ class WbCamera : public WbAbstractCamera { void setup() override; void render() override; bool needToRender() const override; + void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; private: WbSFNode *mFocus; diff --git a/src/webots/nodes/WbContactProperties.cpp b/src/webots/nodes/WbContactProperties.cpp index d179d00b829..b5a7fa07e9c 100644 --- a/src/webots/nodes/WbContactProperties.cpp +++ b/src/webots/nodes/WbContactProperties.cpp @@ -121,6 +121,20 @@ void WbContactProperties::postFinalize() { connect(mMaxContactJoints, &WbSFInt::changed, this, &WbContactProperties::updateMaxContactJoints); } +void WbContactProperties::exportNodeFields(WbWriter &writer) const { + WbBaseNode::exportNodeFields(writer); + + exportSFResourceField(gUrlNames[0], mBumpSound, writer.relativeSoundsPath(), writer); + exportSFResourceField(gUrlNames[1], mRollSound, writer.relativeSoundsPath(), writer); + exportSFResourceField(gUrlNames[2], mSlideSound, writer.relativeSoundsPath(), writer); +} + +QStringList WbContactProperties::customExportedFields() const { + QStringList fields; + fields << "url"; + return fields; +} + void WbContactProperties::updateCoulombFriction() { const int nbElements = mCoulombFriction->size(); if (nbElements < 1 || nbElements > 4) { diff --git a/src/webots/nodes/WbContactProperties.hpp b/src/webots/nodes/WbContactProperties.hpp index 10b9386e41c..a3dce99978f 100644 --- a/src/webots/nodes/WbContactProperties.hpp +++ b/src/webots/nodes/WbContactProperties.hpp @@ -64,6 +64,10 @@ class WbContactProperties : public WbBaseNode { void valuesChanged(); void needToEnableBodies(); +protected: + void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; + private: // user accessible fields WbSFString *mMaterial1; diff --git a/src/webots/nodes/WbMotor.cpp b/src/webots/nodes/WbMotor.cpp index d5b0dcfe4d0..19cfe3b2385 100644 --- a/src/webots/nodes/WbMotor.cpp +++ b/src/webots/nodes/WbMotor.cpp @@ -853,3 +853,15 @@ QList WbMotor::findClosestDescendantNodesWithDedicatedWrenNo list << static_cast(it.next()); return list; } + +void WbMotor::exportNodeFields(WbWriter &writer) const { + WbJointDevice::exportNodeFields(writer); + + exportSFResourceField("sound", mSound, writer.relativeSoundsPath(), writer); +} + +QStringList WbMotor::customExportedFields() const { + QStringList fields; + fields << "sound"; + return fields; +} diff --git a/src/webots/nodes/WbMotor.hpp b/src/webots/nodes/WbMotor.hpp index 9d7bc97a0c8..48f5452456a 100644 --- a/src/webots/nodes/WbMotor.hpp +++ b/src/webots/nodes/WbMotor.hpp @@ -100,6 +100,9 @@ class WbMotor : public WbJointDevice { void enableMotorFeedback(int rate); virtual double computeFeedback() const = 0; + void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; + protected slots: void updateMaxForceOrTorque(); void updateMinAndMaxPosition(); diff --git a/src/webots/nodes/WbSkin.cpp b/src/webots/nodes/WbSkin.cpp index e8d9be9825b..25a782c8c20 100644 --- a/src/webots/nodes/WbSkin.cpp +++ b/src/webots/nodes/WbSkin.cpp @@ -527,6 +527,18 @@ void WbSkin::reset(const QString &id) { } } +void WbSkin::exportNodeFields(WbWriter &writer) const { + WbBaseNode::exportNodeFields(writer); + + exportSFResourceField("modelUrl", mModelUrl, writer.relativeMeshesPath(), writer); +} + +QStringList WbSkin::customExportedFields() const { + QStringList fields; + fields << "modelUrl"; + return fields; +} + void WbSkin::updateModel() { applyTranslationToWren(); applyRotationToWren(); diff --git a/src/webots/nodes/WbSkin.hpp b/src/webots/nodes/WbSkin.hpp index 340a3a6c506..b39edaef082 100644 --- a/src/webots/nodes/WbSkin.hpp +++ b/src/webots/nodes/WbSkin.hpp @@ -67,6 +67,10 @@ class WbSkin : public WbBaseNode, public WbAbstractPose, public WbDevice { signals: void wrenMaterialChanged(); +protected: + void exportNodeFields(WbWriter &writer) const override; + QStringList customExportedFields() const override; + private: WbSkin &operator=(const WbSkin &); // non copyable WbNode *clone() const override { return new WbSkin(*this); } diff --git a/src/webots/nodes/WbTrackWheel.cpp b/src/webots/nodes/WbTrackWheel.cpp index a6288a6e642..9d26d3b5d54 100644 --- a/src/webots/nodes/WbTrackWheel.cpp +++ b/src/webots/nodes/WbTrackWheel.cpp @@ -106,9 +106,8 @@ QStringList WbTrackWheel::fieldsToSynchronizeWithW3d() const { } void WbTrackWheel::exportNodeFields(WbWriter &writer) const { - if (!writer.isW3d()) - return; - WbBaseNode::exportNodeFields(writer); - writer << " rotation=\'" << mRotation->value() << "\'"; + + if (!writer.isW3d()) + writer << " rotation=\'" << mRotation->value() << "\'"; } diff --git a/src/webots/vrml/WbWriter.hpp b/src/webots/vrml/WbWriter.hpp index 408e3d905d3..c766d9b383f 100644 --- a/src/webots/vrml/WbWriter.hpp +++ b/src/webots/vrml/WbWriter.hpp @@ -91,6 +91,7 @@ class WbWriter { static QString relativeTexturesPath() { return "textures/"; } static QString relativeMeshesPath() { return "meshes/"; } + static QString relativeSoundsPath() { return "sounds/"; } private: void setType(); From 86f23370b7674316a873cd049651c7be33de52e2 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 16:27:46 -0700 Subject: [PATCH 06/14] remove old url resolution code --- src/webots/scene_tree/WbSceneTree.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/webots/scene_tree/WbSceneTree.cpp b/src/webots/scene_tree/WbSceneTree.cpp index c04698b5306..71765c4def0 100644 --- a/src/webots/scene_tree/WbSceneTree.cpp +++ b/src/webots/scene_tree/WbSceneTree.cpp @@ -750,20 +750,6 @@ void WbSceneTree::convertProtoToBaseNode(bool rootOnly) { writer.setRootNode(NULL); currentNode->write(writer); - // relative urls that get exposed by the conversion need to be changed to remote ones - QRegularExpressionMatchIterator it = WbUrl::vrmlResourceRegex().globalMatch(nodeString); - while (it.hasNext()) { - const QRegularExpressionMatch match = it.next(); - if (match.hasMatch()) { - QString asset = match.captured(0); - asset.replace("\"", ""); - if (!WbUrl::isWeb(asset) && QDir::isRelativePath(asset)) { - QString newUrl = QString("\"%1\"").arg(WbUrl::combinePaths(asset, currentNode->proto()->url())); - nodeString.replace(QString("\"%1\"").arg(asset), newUrl.replace(WbStandardPaths::webotsHomePath(), "webots://")); - } - } - } - const bool skipTemplateRegeneration = WbVrmlNodeUtilities::findUpperTemplateNeedingRegenerationFromField(parentField, parentNode); if (skipTemplateRegeneration) From 9b9de47fc6f4300b169a621c7dfd642b6fcb05d3 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 16:38:59 -0700 Subject: [PATCH 07/14] revert extraneous WbURL changes --- src/webots/vrml/WbUrl.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/webots/vrml/WbUrl.cpp b/src/webots/vrml/WbUrl.cpp index 3f32a1e9491..c7f9098af4c 100644 --- a/src/webots/vrml/WbUrl.cpp +++ b/src/webots/vrml/WbUrl.cpp @@ -58,18 +58,17 @@ QString WbUrl::missing(const QString &url) { return ""; } -QString WbUrl::computePath(const WbNode *node, const QString &field, const WbMFString *urlField, int index, bool showWarning, - bool disableCache) { +QString WbUrl::computePath(const WbNode *node, const QString &field, const WbMFString *urlField, int index, bool showWarning) { // check if mUrl is empty if (urlField->size() < 1) return ""; // get the URL at specified index const QString &url = urlField->item(index); - return computePath(node, field, url, showWarning, disableCache); + return computePath(node, field, url, showWarning); } -QString WbUrl::computePath(const WbNode *node, const QString &field, const QString &rawUrl, bool showWarning, bool disableCache) { +QString WbUrl::computePath(const WbNode *node, const QString &field, const QString &rawUrl, bool showWarning) { QString url = resolveUrl(rawUrl); // check if the first URL is empty if (url.isEmpty()) { @@ -96,7 +95,7 @@ QString WbUrl::computePath(const WbNode *node, const QString &field, const QStri // note: derived PROTO are a special case because instances of the intermediary ancestors from which it is defined don't // persist after the build process, hence why we keep track of the scope while building the node itself if (protoNode->proto()->isDerived()) { - if (!disableCache && WbFileUtil::isLocatedInDirectory(f->scope(), WbStandardPaths::cachedAssetsPath())) + if (WbFileUtil::isLocatedInDirectory(f->scope(), WbStandardPaths::cachedAssetsPath())) parentUrl = WbNetwork::instance()->getUrlFromEphemeralCache(f->scope()); else parentUrl = f->scope(); From e3b08725e29b7fb44e8bce3cbc1fd3f321116cc9 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 19:30:46 -0700 Subject: [PATCH 08/14] restore comment --- src/webots/vrml/WbUrl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webots/vrml/WbUrl.cpp b/src/webots/vrml/WbUrl.cpp index c7f9098af4c..e996024276f 100644 --- a/src/webots/vrml/WbUrl.cpp +++ b/src/webots/vrml/WbUrl.cpp @@ -134,6 +134,9 @@ QString WbUrl::resolveUrl(const QString &rawUrl) { QString WbUrl::exportResource(const WbNode *node, const QString &url, const QString &sourcePath, const QString &relativeResourcePath, const WbWriter &writer) { + // in addition to writing the node, we want to ensure that the resource file exists + // at the expected location. If not, we should copy it, possibly creating the expected + // directory structure. const QFileInfo urlFileInfo(url); const QString fileName = urlFileInfo.fileName(); const QString expectedRelativePath = relativeResourcePath + fileName; From 1ca1455b096b6aeba6ee816fadbd4003aa140dbd Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 20:44:09 -0700 Subject: [PATCH 09/14] use WbNode helpers in WbBackground --- src/webots/nodes/WbBackground.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/webots/nodes/WbBackground.cpp b/src/webots/nodes/WbBackground.cpp index 37c2fe79569..5f1454c394b 100644 --- a/src/webots/nodes/WbBackground.cpp +++ b/src/webots/nodes/WbBackground.cpp @@ -708,16 +708,11 @@ void WbBackground::exportNodeFields(WbWriter &writer) const { if (!irradianceFileNames[i].isEmpty()) writer << gIrradianceUrlNames(i) << "='\"" << irradianceFileNames[i] << "\"' "; } - } else if (writer.isProto()) { + } else { for (int i = 0; i < 6; ++i) { exportMFResourceField(gUrlNames(i), mUrlFields[i], writer.relativeTexturesPath(), writer); exportMFResourceField(gIrradianceUrlNames(i), mIrradianceUrlFields[i], writer.relativeTexturesPath(), writer); } - } else { - for (int i = 0; i < 6; ++i) { - findField(gUrlNames(i), true)->write(writer); - findField(gIrradianceUrlNames(i), true)->write(writer); - } } } From 6bc6a76de9183e368fdf78c02032c81fa239133f Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 20:45:02 -0700 Subject: [PATCH 10/14] fix WbTrackWheel if-condition --- src/webots/nodes/WbTrackWheel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webots/nodes/WbTrackWheel.cpp b/src/webots/nodes/WbTrackWheel.cpp index 9d26d3b5d54..b10e3462766 100644 --- a/src/webots/nodes/WbTrackWheel.cpp +++ b/src/webots/nodes/WbTrackWheel.cpp @@ -108,6 +108,6 @@ QStringList WbTrackWheel::fieldsToSynchronizeWithW3d() const { void WbTrackWheel::exportNodeFields(WbWriter &writer) const { WbBaseNode::exportNodeFields(writer); - if (!writer.isW3d()) + if (writer.isW3d()) writer << " rotation=\'" << mRotation->value() << "\'"; } From e12c3ac6ae784797b52bcc30db602841702fd564 Mon Sep 17 00:00:00 2001 From: CoolSpy3 <55305038+CoolSpy3@users.noreply.github.com> Date: Sat, 28 Jun 2025 21:12:04 -0700 Subject: [PATCH 11/14] run clang-format --- src/webots/nodes/WbBackground.cpp | 4 ++-- src/webots/vrml/WbNode.cpp | 14 +++++++++----- src/webots/vrml/WbNode.hpp | 10 +++++----- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/webots/nodes/WbBackground.cpp b/src/webots/nodes/WbBackground.cpp index 5f1454c394b..e623cb0f79b 100644 --- a/src/webots/nodes/WbBackground.cpp +++ b/src/webots/nodes/WbBackground.cpp @@ -697,8 +697,8 @@ void WbBackground::exportNodeFields(WbWriter &writer) const { continue; const QString &resolvedURL = WbUrl::computePath(this, gIrradianceUrlNames(i), mIrradianceUrlFields[i], 0); - irradianceFileNames[i] = exportResource(mIrradianceUrlFields[i]->item(0), resolvedURL, - writer.relativeTexturesPath(), writer); + irradianceFileNames[i] = + exportResource(mIrradianceUrlFields[i]->item(0), resolvedURL, writer.relativeTexturesPath(), writer); } writer << " "; diff --git a/src/webots/vrml/WbNode.cpp b/src/webots/vrml/WbNode.cpp index 3987e1a87d3..695b2f1fd50 100644 --- a/src/webots/vrml/WbNode.cpp +++ b/src/webots/vrml/WbNode.cpp @@ -1246,7 +1246,7 @@ void WbNode::writeExport(WbWriter &writer) const { } QString WbNode::exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, - WbWriter &writer) const { + WbWriter &writer) const { if (WbUrl::isLocalUrl(resolvedURL)) return WbUrl::computeLocalAssetUrl(resolvedURL, writer.isW3d()); else if (WbUrl::isWeb(resolvedURL)) @@ -1259,7 +1259,8 @@ QString WbNode::exportResource(const QString &rawURL, const QString &resolvedURL } } -void WbNode::exportMFResourceField(const QString &fieldName, const WbMFString* value, const QString &relativeResourcePath, WbWriter &writer) const { +void WbNode::exportMFResourceField(const QString &fieldName, const WbMFString *value, const QString &relativeResourcePath, + WbWriter &writer) const { const WbField *originalField = findField(fieldName, true); assert(originalField && originalField->type() == WB_MF_STRING); @@ -1269,7 +1270,8 @@ void WbNode::exportMFResourceField(const QString &fieldName, const WbMFString* v return; } - if (value->size() == 0) return; + if (value->size() == 0) + return; WbField copiedField(*originalField); WbMFString *newValue = dynamic_cast(copiedField.value()); @@ -1283,7 +1285,8 @@ void WbNode::exportMFResourceField(const QString &fieldName, const WbMFString* v copiedField.write(writer); } -void WbNode::exportSFResourceField(const QString &fieldName, const WbSFString* value, const QString &relativeResourcePath, WbWriter &writer) const { +void WbNode::exportSFResourceField(const QString &fieldName, const WbSFString *value, const QString &relativeResourcePath, + WbWriter &writer) const { const WbField *originalField = findField(fieldName, true); assert(originalField && originalField->type() == WB_SF_STRING); @@ -1294,7 +1297,8 @@ void WbNode::exportSFResourceField(const QString &fieldName, const WbSFString* v } const QString &rawURL = value->value(); - if (rawURL.isEmpty()) return; + if (rawURL.isEmpty()) + return; WbField copiedField(*originalField); WbSFString *newValue = dynamic_cast(copiedField.value()); diff --git a/src/webots/vrml/WbNode.hpp b/src/webots/vrml/WbNode.hpp index 883d59300c4..ca166e2cc94 100644 --- a/src/webots/vrml/WbNode.hpp +++ b/src/webots/vrml/WbNode.hpp @@ -322,12 +322,12 @@ class WbNode : public QObject { // Helper to handle exporting fields with resources which reference external files QString exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, - WbWriter &writer) const; + WbWriter &writer) const; // Wrappers for the most common use case (simply exporting a field with no additional processing) - void exportMFResourceField(const QString &fieldName, const WbMFString* value, const QString &relativeResourcePath, - WbWriter &writer) const; - void exportSFResourceField(const QString &fieldName, const WbSFString* value, const QString &relativeResourcePath, - WbWriter &writer) const; + void exportMFResourceField(const QString &fieldName, const WbMFString *value, const QString &relativeResourcePath, + WbWriter &writer) const; + void exportSFResourceField(const QString &fieldName, const WbSFString *value, const QString &relativeResourcePath, + WbWriter &writer) const; // Methods related to URDF export const WbNode *findUrdfLinkRoot() const; // Finds first upper Webots node that is considered as URDF link From 6d957d651e023bc3488351cd0cde3620740de147 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 21:21:24 -0700 Subject: [PATCH 12/14] update the changelog --- docs/reference/changelog-r2025.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/reference/changelog-r2025.md b/docs/reference/changelog-r2025.md index 114982338d7..f84cecc3612 100644 --- a/docs/reference/changelog-r2025.md +++ b/docs/reference/changelog-r2025.md @@ -1,10 +1,15 @@ # Webots R2025 Change Log ## Webots R2025b + - Enhancements + - `WbCamera`, `WbContactProperties`, `WbMotor`, and `WbSkin` will now locally-download their resources if-necessary (like other nodes) when steaming or exporting to `w3d` ([#6856](https://github.com/cyberbotics/webots/pull/6856)). - Bug Fixes - Fixed a bug preventing the `webots-controller` executable from running on arm-based mac devices ([#6806](https://github.com/cyberbotics/webots/pull/6806)). - Fixed a bug causing Webots to crash when multiple sounds were used with a [Speaker](speaker.md) node ([#6843](https://github.com/cyberbotics/webots/pull/6843)). - Fixed a bug causing the "Reload/Reset" buttons in the controller recompilation popup to not work on Windows ([#6844](https://github.com/cyberbotics/webots/pull/6844)). + - Fixed resolution of relative paths when converting protos to their base nodes ([#6856](https://github.com/cyberbotics/webots/pull/6856)). + - **As a result of this change, relative paths that are not within a Webots-recognized resource field (ex. `url`) will no longer be updated when a proto is converted to its base nodes.** This should not affect most users. + - Fixed a bug causing `TrackWheel` nodes to lose their field values when used in a proto converted to a base node ([#6856](https://github.com/cyberbotics/webots/pull/6856)). ## Webots R2025a Released on January 31st, 2025. From 18bf78554d1af0a449e995e7bfaaab2f432ba669 Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Sat, 28 Jun 2025 21:26:30 -0700 Subject: [PATCH 13/14] use const reference in WbNode::exportResource --- src/webots/vrml/WbNode.cpp | 2 +- src/webots/vrml/WbNode.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webots/vrml/WbNode.cpp b/src/webots/vrml/WbNode.cpp index 695b2f1fd50..6dbda04220b 100644 --- a/src/webots/vrml/WbNode.cpp +++ b/src/webots/vrml/WbNode.cpp @@ -1246,7 +1246,7 @@ void WbNode::writeExport(WbWriter &writer) const { } QString WbNode::exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, - WbWriter &writer) const { + const WbWriter &writer) const { if (WbUrl::isLocalUrl(resolvedURL)) return WbUrl::computeLocalAssetUrl(resolvedURL, writer.isW3d()); else if (WbUrl::isWeb(resolvedURL)) diff --git a/src/webots/vrml/WbNode.hpp b/src/webots/vrml/WbNode.hpp index ca166e2cc94..0c38407a306 100644 --- a/src/webots/vrml/WbNode.hpp +++ b/src/webots/vrml/WbNode.hpp @@ -322,7 +322,7 @@ class WbNode : public QObject { // Helper to handle exporting fields with resources which reference external files QString exportResource(const QString &rawURL, const QString &resolvedURL, const QString &relativeResourcePath, - WbWriter &writer) const; + const WbWriter &writer) const; // Wrappers for the most common use case (simply exporting a field with no additional processing) void exportMFResourceField(const QString &fieldName, const WbMFString *value, const QString &relativeResourcePath, WbWriter &writer) const; From d2902059a5ed4382fa68bc1e12dc052b29b3e95b Mon Sep 17 00:00:00 2001 From: CoolSpy3 Date: Mon, 14 Jul 2025 01:50:05 -0700 Subject: [PATCH 14/14] fix bad merge --- docs/reference/changelog-r2025.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/changelog-r2025.md b/docs/reference/changelog-r2025.md index ec465c36e5c..6aacb0f6b87 100644 --- a/docs/reference/changelog-r2025.md +++ b/docs/reference/changelog-r2025.md @@ -5,6 +5,7 @@ - `WbCamera`, `WbContactProperties`, `WbMotor`, and `WbSkin` will now locally-download their resources if-necessary (like other nodes) when steaming or exporting to `w3d` ([#6856](https://github.com/cyberbotics/webots/pull/6856)). - Added missing import libraries on Windows ([#6753](https://github.com/cyberbotics/webots/pull/6753)). - Added some missing function definitions to the existing Windows libraries ([#6753](https://github.com/cyberbotics/webots/pull/6753)). + - Cleanup - **Removed `libController.a` and `libCppController.a` libraries on Windows. Please use `Controller.lib` and `CppController.lib` instead ([#6753](https://github.com/cyberbotics/webots/pull/6753)).** - Bug Fixes - Fixed a bug preventing the `webots-controller` executable from running on arm-based mac devices ([#6806](https://github.com/cyberbotics/webots/pull/6806)).