Skip to content

Commit ec04f7e

Browse files
authored
Rework Resource Handling in Node Exports (#6856)
* unify existing export code * add simplified normal use case helper * cleanup existing export methods * prevent duplicate exports * add export support for resources that don't already have it * remove old url resolution code * revert extraneous WbURL changes * restore comment * use WbNode helpers in WbBackground * fix WbTrackWheel if-condition * run clang-format * update the changelog * use const reference in WbNode::exportResource * fix bad merge
1 parent f531fb5 commit ec04f7e

24 files changed

+210
-143
lines changed

docs/reference/changelog-r2025.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Webots R2025b
44
- Enhancements
5+
- `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)).
56
- Added implementations of `wbu_system_tmpdir` and `wbu_system_webots_instance_path` to the MATLAB API ([#6756](https://github.com/cyberbotics/webots/pull/6756)).
67
- Added missing import libraries on Windows ([#6753](https://github.com/cyberbotics/webots/pull/6753)).
78
- Added some missing function definitions to the existing Windows libraries ([#6753](https://github.com/cyberbotics/webots/pull/6753)).
@@ -11,6 +12,9 @@
1112
- Fixed a bug preventing the `webots-controller` executable from running on arm-based mac devices ([#6806](https://github.com/cyberbotics/webots/pull/6806)).
1213
- 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)).
1314
- 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)).
15+
- Fixed resolution of relative paths when converting protos to their base nodes ([#6856](https://github.com/cyberbotics/webots/pull/6856)).
16+
- **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.
17+
- 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)).
1418
- Fixed a bug causing supervisors to occasionally read stale field values after the simulation was reset ([#6758](https://github.com/cyberbotics/webots/pull/6758)).
1519
- Fixed a bug causing Webots to occasionally crash when unloading a world ([#6857](https://github.com/cyberbotics/webots/pull/6857)).
1620

src/webots/nodes/WbBackground.cpp

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -679,48 +679,26 @@ WbRgb WbBackground::skyColor() const {
679679
}
680680

681681
void WbBackground::exportNodeFields(WbWriter &writer) const {
682-
if (writer.isWebots()) {
683-
WbBaseNode::exportNodeFields(writer);
684-
return;
685-
}
682+
WbBaseNode::exportNodeFields(writer);
686683

687684
if (writer.isW3d()) {
688685
QString backgroundFileNames[6];
689686
for (int i = 0; i < 6; ++i) {
690687
if (mUrlFields[i]->size() == 0)
691688
continue;
692689

693-
WbField urlFieldCopy(*findField(gUrlNames(i), true));
694-
const QString &imagePath = WbUrl::computePath(this, gUrlNames(i), mUrlFields[i]->item(0));
695-
if (WbUrl::isLocalUrl(imagePath))
696-
backgroundFileNames[i] = WbUrl::computeLocalAssetUrl(imagePath, writer.isW3d());
697-
else if (WbUrl::isWeb(imagePath))
698-
backgroundFileNames[i] = imagePath;
699-
else {
700-
if (writer.isWritingToFile())
701-
backgroundFileNames[i] = WbUrl::exportResource(this, imagePath, imagePath, writer.relativeTexturesPath(), writer);
702-
else
703-
backgroundFileNames[i] = WbUrl::expressRelativeToWorld(imagePath);
704-
}
690+
const QString &resolvedURL = WbUrl::computePath(this, gUrlNames(i), mUrlFields[i], 0);
691+
backgroundFileNames[i] = exportResource(mUrlFields[i]->item(0), resolvedURL, writer.relativeTexturesPath(), writer);
705692
}
706693

707694
QString irradianceFileNames[6];
708695
for (int i = 0; i < 6; ++i) {
709696
if (mIrradianceUrlFields[i]->size() == 0)
710697
continue;
711698

712-
const QString &irradiancePath = WbUrl::computePath(this, gIrradianceUrlNames(i), mIrradianceUrlFields[i]->item(0));
713-
if (WbUrl::isLocalUrl(irradiancePath))
714-
irradianceFileNames[i] = WbUrl::computeLocalAssetUrl(irradiancePath, writer.isW3d());
715-
else if (WbUrl::isWeb(irradiancePath))
716-
irradianceFileNames[i] = irradiancePath;
717-
else {
718-
if (writer.isWritingToFile())
719-
irradianceFileNames[i] =
720-
WbUrl::exportResource(this, irradiancePath, irradiancePath, writer.relativeTexturesPath(), writer);
721-
else
722-
irradianceFileNames[i] = WbUrl::expressRelativeToWorld(irradiancePath);
723-
}
699+
const QString &resolvedURL = WbUrl::computePath(this, gIrradianceUrlNames(i), mIrradianceUrlFields[i], 0);
700+
irradianceFileNames[i] =
701+
exportResource(mIrradianceUrlFields[i]->item(0), resolvedURL, writer.relativeTexturesPath(), writer);
724702
}
725703

726704
writer << " ";
@@ -730,7 +708,19 @@ void WbBackground::exportNodeFields(WbWriter &writer) const {
730708
if (!irradianceFileNames[i].isEmpty())
731709
writer << gIrradianceUrlNames(i) << "='\"" << irradianceFileNames[i] << "\"' ";
732710
}
711+
} else {
712+
for (int i = 0; i < 6; ++i) {
713+
exportMFResourceField(gUrlNames(i), mUrlFields[i], writer.relativeTexturesPath(), writer);
714+
exportMFResourceField(gIrradianceUrlNames(i), mIrradianceUrlFields[i], writer.relativeTexturesPath(), writer);
715+
}
733716
}
717+
}
734718

735-
WbBaseNode::exportNodeFields(writer);
719+
QStringList WbBackground::customExportedFields() const {
720+
QStringList fields;
721+
for (int i = 0; i < 6; ++i) {
722+
fields << gUrlNames(i);
723+
fields << gIrradianceUrlNames(i);
724+
}
725+
return fields;
736726
}

src/webots/nodes/WbBackground.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class WbBackground : public WbBaseNode {
5959

6060
protected:
6161
void exportNodeFields(WbWriter &writer) const override;
62+
QStringList customExportedFields() const override;
6263

6364
private:
6465
static QList<WbBackground *> cBackgroundList;

src/webots/nodes/WbCadShape.cpp

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -568,46 +568,31 @@ const WbVector3 WbCadShape::absoluteScale() const {
568568
}
569569

570570
void WbCadShape::exportNodeFields(WbWriter &writer) const {
571-
if (!(writer.isW3d() || writer.isProto()))
572-
return;
571+
WbBaseNode::exportNodeFields(writer);
573572

574573
if (mUrl->size() == 0)
575574
return;
576575

577-
WbBaseNode::exportNodeFields(writer);
576+
if (!(writer.isW3d() || writer.isProto())) {
577+
findField("url", true)->write(writer);
578+
return;
579+
}
578580

579581
// export model
580582
WbField urlFieldCopy(*findField("url", true));
581583
for (int i = 0; i < mUrl->size(); ++i) {
582-
const QString &completeUrl = WbUrl::computePath(this, "url", mUrl, i);
584+
const QString &resolvedURL = WbUrl::computePath(this, "url", mUrl, i);
583585
WbMFString *urlFieldValue = dynamic_cast<WbMFString *>(urlFieldCopy.value());
584-
if (WbUrl::isLocalUrl(completeUrl))
585-
urlFieldValue->setItem(i, WbUrl::computeLocalAssetUrl(completeUrl, writer.isW3d()));
586-
else if (WbUrl::isWeb(completeUrl))
587-
urlFieldValue->setItem(i, completeUrl);
588-
else {
589-
urlFieldValue->setItem(
590-
i, writer.isWritingToFile() ? WbUrl::exportMesh(this, mUrl, i, writer) : WbUrl::expressRelativeToWorld(completeUrl));
591-
}
586+
urlFieldValue->setItem(i, exportResource(mUrl->item(i), resolvedURL, writer.relativeMeshesPath(), writer));
592587
}
593588

594589
// export materials
595590
if (writer.isW3d()) { // only needs to be included in the w3d, when converting to base node it shouldn't be included
596-
const QString &parentUrl = WbUrl::computePath(this, "url", mUrl->item(0));
591+
const QString &parentUrl = WbUrl::computePath(this, "url", mUrl, 0);
597592
for (const QString &material : objMaterialList(parentUrl)) {
598593
QString materialUrl = WbUrl::combinePaths(material, parentUrl);
599594
WbMFString *urlFieldValue = dynamic_cast<WbMFString *>(urlFieldCopy.value());
600-
if (WbUrl::isLocalUrl(materialUrl))
601-
urlFieldValue->addItem(WbUrl::computeLocalAssetUrl(materialUrl, writer.isW3d()));
602-
else if (WbUrl::isWeb(materialUrl))
603-
urlFieldValue->addItem(materialUrl);
604-
else {
605-
if (writer.isWritingToFile())
606-
urlFieldValue->addItem(
607-
WbUrl::exportResource(this, materialUrl, materialUrl, writer.relativeMeshesPath(), writer, false));
608-
else
609-
urlFieldValue->addItem(WbUrl::expressRelativeToWorld(materialUrl));
610-
}
595+
urlFieldValue->addItem(exportResource(material, materialUrl, writer.relativeMeshesPath(), writer));
611596
}
612597
}
613598

@@ -618,6 +603,12 @@ void WbCadShape::exportNodeFields(WbWriter &writer) const {
618603
urlFieldCopy.write(writer);
619604
}
620605

606+
QStringList WbCadShape::customExportedFields() const {
607+
QStringList fields;
608+
fields << "url";
609+
return fields;
610+
}
611+
621612
QString WbCadShape::cadPath() const {
622613
return WbUrl::computePath(this, "url", mUrl, 0);
623614
}

src/webots/nodes/WbCadShape.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class WbCadShape : public WbBaseNode {
5656

5757
protected:
5858
void exportNodeFields(WbWriter &writer) const override;
59+
QStringList customExportedFields() const override;
5960
WbBoundingSphere *boundingSphere() const override { return mBoundingSphere; }
6061
void recomputeBoundingSphere() const;
6162

src/webots/nodes/WbCamera.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,18 @@ WbVector3 WbCamera::urdfRotation(const WbMatrix3 &rotationMatrix) const {
10171017
return eulerRotation;
10181018
}
10191019

1020+
void WbCamera::exportNodeFields(WbWriter &writer) const {
1021+
WbAbstractCamera::exportNodeFields(writer);
1022+
1023+
exportSFResourceField("noiseMaskUrl", mNoiseMaskUrl, writer.relativeMeshesPath(), writer);
1024+
}
1025+
1026+
QStringList WbCamera::customExportedFields() const {
1027+
QStringList fields;
1028+
fields << "noiseMaskUrl";
1029+
return fields;
1030+
}
1031+
10201032
/////////////////////
10211033
// Update methods //
10221034
/////////////////////

src/webots/nodes/WbCamera.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class WbCamera : public WbAbstractCamera {
6767
void setup() override;
6868
void render() override;
6969
bool needToRender() const override;
70+
void exportNodeFields(WbWriter &writer) const override;
71+
QStringList customExportedFields() const override;
7072

7173
private:
7274
WbSFNode *mFocus;

src/webots/nodes/WbContactProperties.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ void WbContactProperties::postFinalize() {
121121
connect(mMaxContactJoints, &WbSFInt::changed, this, &WbContactProperties::updateMaxContactJoints);
122122
}
123123

124+
void WbContactProperties::exportNodeFields(WbWriter &writer) const {
125+
WbBaseNode::exportNodeFields(writer);
126+
127+
exportSFResourceField(gUrlNames[0], mBumpSound, writer.relativeSoundsPath(), writer);
128+
exportSFResourceField(gUrlNames[1], mRollSound, writer.relativeSoundsPath(), writer);
129+
exportSFResourceField(gUrlNames[2], mSlideSound, writer.relativeSoundsPath(), writer);
130+
}
131+
132+
QStringList WbContactProperties::customExportedFields() const {
133+
QStringList fields;
134+
fields << "url";
135+
return fields;
136+
}
137+
124138
void WbContactProperties::updateCoulombFriction() {
125139
const int nbElements = mCoulombFriction->size();
126140
if (nbElements < 1 || nbElements > 4) {

src/webots/nodes/WbContactProperties.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class WbContactProperties : public WbBaseNode {
6464
void valuesChanged();
6565
void needToEnableBodies();
6666

67+
protected:
68+
void exportNodeFields(WbWriter &writer) const override;
69+
QStringList customExportedFields() const override;
70+
6771
private:
6872
// user accessible fields
6973
WbSFString *mMaterial1;

src/webots/nodes/WbImageTexture.cpp

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -528,39 +528,28 @@ bool WbImageTexture::exportNodeHeader(WbWriter &writer) const {
528528
void WbImageTexture::exportNodeFields(WbWriter &writer) const {
529529
WbBaseNode::exportNodeFields(writer);
530530

531-
// export to ./textures folder relative to writer path
532-
WbField urlFieldCopy(*findField("url", true));
533-
for (int i = 0; i < mUrl->size(); ++i) {
534-
QString completeUrl = WbUrl::computePath(this, "url", mUrl, i);
535-
WbMFString *urlFieldValue = dynamic_cast<WbMFString *>(urlFieldCopy.value());
536-
if (WbUrl::isLocalUrl(completeUrl))
537-
urlFieldValue->setItem(i, WbUrl::computeLocalAssetUrl(completeUrl, writer.isW3d()));
538-
else if (WbUrl::isWeb(completeUrl))
539-
urlFieldValue->setItem(i, completeUrl);
540-
else {
541-
if (writer.isWritingToFile())
542-
urlFieldValue->setItem(i, WbUrl::exportTexture(this, mUrl, i, writer));
543-
else
544-
urlFieldValue->setItem(i, WbUrl::expressRelativeToWorld(completeUrl));
545-
}
546-
}
547-
548-
urlFieldCopy.write(writer);
531+
exportMFResourceField("url", mUrl, writer.relativeTexturesPath(), writer);
549532

550533
if (writer.isW3d()) {
551534
if (!mRole.isEmpty())
552535
writer << " role=\'" << mRole << "\'";
553536
}
554537
}
555538

539+
QStringList WbImageTexture::customExportedFields() const {
540+
QStringList fields;
541+
fields << "url";
542+
return fields;
543+
}
544+
556545
void WbImageTexture::exportShallowNode(const WbWriter &writer) const {
557546
if (!writer.isW3d() || mUrl->size() == 0)
558547
return;
559548

560549
// note: the texture of the shallow nodes needs to be exported only if the URL is locally defined but not of type
561550
// 'webots://' since this case would be converted to a remote one that targets the current branch
562551
if (!WbUrl::isWeb(mUrl->item(0)) && !WbUrl::isLocalUrl(mUrl->item(0)) && !WbWorld::isW3dStreaming())
563-
WbUrl::exportTexture(this, mUrl, 0, writer);
552+
WbUrl::exportResource(this, mUrl->item(0), WbUrl::computePath(this, "url", mUrl, 0), writer.relativeTexturesPath(), writer);
564553
}
565554

566555
QStringList WbImageTexture::fieldsToSynchronizeWithW3d() const {

0 commit comments

Comments
 (0)