Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/reference/changelog-r2025.md
Original file line number Diff line number Diff line change
@@ -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)).
- Fixed a bug causing Webots to occasionally crash when unloading a world ([#6857](https://github.com/cyberbotics/webots/pull/6857)).

## Webots R2025a
Expand Down
48 changes: 19 additions & 29 deletions src/webots/nodes/WbBackground.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -679,48 +679,26 @@ 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];
for (int i = 0; i < 6; ++i) {
if (mUrlFields[i]->size() == 0)
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];
for (int i = 0; i < 6; ++i) {
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 << " ";
Expand All @@ -730,7 +708,19 @@ void WbBackground::exportNodeFields(WbWriter &writer) const {
if (!irradianceFileNames[i].isEmpty())
writer << gIrradianceUrlNames(i) << "='\"" << irradianceFileNames[i] << "\"' ";
}
} else {
for (int i = 0; i < 6; ++i) {
exportMFResourceField(gUrlNames(i), mUrlFields[i], writer.relativeTexturesPath(), writer);
exportMFResourceField(gIrradianceUrlNames(i), mIrradianceUrlFields[i], writer.relativeTexturesPath(), writer);
}
}
}

WbBaseNode::exportNodeFields(writer);
QStringList WbBackground::customExportedFields() const {
QStringList fields;
for (int i = 0; i < 6; ++i) {
fields << gUrlNames(i);
fields << gIrradianceUrlNames(i);
}
return fields;
}
1 change: 1 addition & 0 deletions src/webots/nodes/WbBackground.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class WbBackground : public WbBaseNode {

protected:
void exportNodeFields(WbWriter &writer) const override;
QStringList customExportedFields() const override;

private:
static QList<WbBackground *> cBackgroundList;
Expand Down
39 changes: 15 additions & 24 deletions src/webots/nodes/WbCadShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,46 +568,31 @@ 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;

WbBaseNode::exportNodeFields(writer);
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) {
const QString &completeUrl = WbUrl::computePath(this, "url", mUrl, i);
const QString &resolvedURL = WbUrl::computePath(this, "url", mUrl, i);
WbMFString *urlFieldValue = dynamic_cast<WbMFString *>(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<WbMFString *>(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));
}
}

Expand All @@ -618,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);
}
Expand Down
1 change: 1 addition & 0 deletions src/webots/nodes/WbCadShape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
12 changes: 12 additions & 0 deletions src/webots/nodes/WbCamera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 //
/////////////////////
Expand Down
2 changes: 2 additions & 0 deletions src/webots/nodes/WbCamera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions src/webots/nodes/WbContactProperties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/webots/nodes/WbContactProperties.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 8 additions & 19 deletions src/webots/nodes/WbImageTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,39 +528,28 @@ 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) {
QString completeUrl = WbUrl::computePath(this, "url", mUrl, i);
WbMFString *urlFieldValue = dynamic_cast<WbMFString *>(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));
}
}

urlFieldCopy.write(writer);
exportMFResourceField("url", mUrl, writer.relativeTexturesPath(), writer);

if (writer.isW3d()) {
if (!mRole.isEmpty())
writer << " role=\'" << mRole << "\'";
}
}

QStringList WbImageTexture::customExportedFields() const {
QStringList fields;
fields << "url";
return fields;
}

void WbImageTexture::exportShallowNode(const WbWriter &writer) const {
if (!writer.isW3d() || mUrl->size() == 0)
return;

// 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 {
Expand Down
1 change: 1 addition & 0 deletions src/webots/nodes/WbImageTexture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 7 additions & 23 deletions src/webots/nodes/WbMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,31 +401,15 @@ void WbMesh::updateMaterialIndex() {
}

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 &completeUrl = WbUrl::computePath(this, "url", mUrl, i);
WbMFString *urlFieldValue = dynamic_cast<WbMFString *>(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));
}
}
WbGeometry::exportNodeFields(writer);

urlFieldCopy.write(writer);
exportMFResourceField("url", mUrl, writer.relativeMeshesPath(), writer);
}

WbGeometry::exportNodeFields(writer);
QStringList WbMesh::customExportedFields() const {
QStringList fields;
fields << "url";
return fields;
}

QStringList WbMesh::fieldsToSynchronizeWithW3d() const {
Expand Down
1 change: 1 addition & 0 deletions src/webots/nodes/WbMesh.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class WbMesh : public WbTriangleMeshGeometry {

protected:
void exportNodeFields(WbWriter &writer) const override;
QStringList customExportedFields() const override;

private:
// user accessible fields
Expand Down
12 changes: 12 additions & 0 deletions src/webots/nodes/WbMotor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -853,3 +853,15 @@ QList<const WbBaseNode *> WbMotor::findClosestDescendantNodesWithDedicatedWrenNo
list << static_cast<WbBaseNode *>(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;
}
3 changes: 3 additions & 0 deletions src/webots/nodes/WbMotor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
12 changes: 12 additions & 0 deletions src/webots/nodes/WbSkin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions src/webots/nodes/WbSkin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down
Loading