diff --git a/docs/reference/changelog-r2024.md b/docs/reference/changelog-r2024.md index 7fe1315b60d..c0853d9d6a3 100644 --- a/docs/reference/changelog-r2024.md +++ b/docs/reference/changelog-r2024.md @@ -4,6 +4,7 @@ Released on December **th, 2023. - New Features - **Change the name of the web scene format from `X3D` to `W3D` ([#6280](https://github.com/cyberbotics/webots/pull/6280)).** + - Added a method to include all subtypes of a node type in a PROTO field restriction ([#6574](https://github.com/cyberbotics/webots/pull/6574)). - Enhancements - Improved the image range of the rotating [Lidar](lidar.md) ([#6324](https://github.com/cyberbotics/webots/pull/6324)). - Cleanup @@ -12,4 +13,3 @@ Released on December **th, 2023. - Fixed error message on Windows when `libssl-3-x64.dll` was added to `PATH` ([#6553](https://github.com/cyberbotics/webots/pull/6553)). - Fixed length of arrays returned by `getPose()` in Java ([#6556](https://github.com/cyberbotics/webots/pull/6556)). - Fixed length of arrays returned by `CameraRecognitionObject.getColors()` in Java ([#6564](https://github.com/cyberbotics/webots/pull/6564)) - diff --git a/docs/reference/proto-definition.md b/docs/reference/proto-definition.md index 3323b19b38d..8535b3c4d02 100644 --- a/docs/reference/proto-definition.md +++ b/docs/reference/proto-definition.md @@ -77,11 +77,15 @@ PROTO MyProto [ field SFString name "my proto" field SFColor{0 0 0, 0.5 0.5 0.5, 1 1 1} color 0.5 0.5 0.5 field SFNode physics NULL - field MFNode{Solid{}, Pose{}} extensionSlot [] + field MFNode{Solid{}+, Pose{}} extensionSlot [] ] ``` -In this example, the `color` field value can only be `0 0 0`, `0.5 0.5 0.5` or `1 1 1` and the `extensionSlot` field can only accept [Solid](../reference/solid.md) and [Pose](../reference/pose.md) nodes. +For `SFNode`/`MFNode` fields, the `{}+` syntax allows the field to also accept nodes which derive from a specific node type. + +In this example, the `color` field value can only be `0 0 0`, `0.5 0.5 0.5` or `1 1 1`, and the `extensionSlot` field can only accept [Pose](pose.md) nodes, PROTOs whose base type is [Pose](pose.md), [Solid](solid.md) nodes, nodes derived from [Solid](solid.md), and PROTOs derived from [Solid](solid.md) or a type that inherits from [Solid](../reference/solid.md). Note that because `Pose{}` is not followed by a `+`, `extensionSlot` does not accept nodes that derive from [Pose](pose.md) (e.g. [Transform](transform.md) or [Fluid](fluid.md)) or PROTOs whose base type is not [Pose](pose.md). + +Because [Solid](solid.md) derives from [Pose](pose.md), we could have allowed all the same node types by using `MFNode{Pose{}+}`, but this would also allow other descendants of [Pose](pose.md) such as [Transform](transform.md) or [Fluid](fluid.md). ### IS Statements diff --git a/resources/nodes/Accelerometer.wrl b/resources/nodes/Accelerometer.wrl index 86e0fd29865..8bf495bd30f 100644 --- a/resources/nodes/Accelerometer.wrl +++ b/resources/nodes/Accelerometer.wrl @@ -2,6 +2,7 @@ # simulation. The acceleration is measured along the 3 axes (X, Y and Z) and is # expressed in m/s^2. It is mostly used to measure the direction of the # gravity, but can be used for many other purposes. +# parent: Solid Accelerometer { #fields that inherit from the Solid node: diff --git a/resources/nodes/Altimeter.wrl b/resources/nodes/Altimeter.wrl index 01b806520a2..47574edc71a 100644 --- a/resources/nodes/Altimeter.wrl +++ b/resources/nodes/Altimeter.wrl @@ -1,4 +1,5 @@ # The Altimeter node can be used to determine the global altitude of a robot or of a robot part. +# parent: Solid Altimeter { #fields that inherit from the Solid node: diff --git a/resources/nodes/BallJoint.wrl b/resources/nodes/BallJoint.wrl index 1a6bf4ef9eb..eadf9602170 100644 --- a/resources/nodes/BallJoint.wrl +++ b/resources/nodes/BallJoint.wrl @@ -1,4 +1,5 @@ # A BallJoint node can be used to simulate a rotating motion with 3 DOF (ball and socket). +# parent: Hinge2Joint BallJoint { field SFNode jointParameters NULL # BallJointParameters specifying the joint anchor and spring and damper constants, minStop, maxStop related to the first axis diff --git a/resources/nodes/BallJointParameters.wrl b/resources/nodes/BallJointParameters.wrl index 0bcc380eb1c..c8b53346612 100644 --- a/resources/nodes/BallJointParameters.wrl +++ b/resources/nodes/BallJointParameters.wrl @@ -1,4 +1,5 @@ # The BallJointParamaters node defines the parameters of a BallJoint node. +# parent: JointParameters BallJointParameters { field SFFloat position 0 # current position (rad) diff --git a/resources/nodes/Billboard.wrl b/resources/nodes/Billboard.wrl index bb824ac60a2..d7ca5797d52 100644 --- a/resources/nodes/Billboard.wrl +++ b/resources/nodes/Billboard.wrl @@ -1,5 +1,6 @@ # A Billboard node contains children nodes that rotate and translate automatically to face the viewpoint. # It is otherwise similar to a Group node. +# parent: Group Billboard { #field that inherits from the Group node: diff --git a/resources/nodes/Camera.wrl b/resources/nodes/Camera.wrl index 504b3086cde..7664f285206 100644 --- a/resources/nodes/Camera.wrl +++ b/resources/nodes/Camera.wrl @@ -1,4 +1,5 @@ # The Camera node is used to model an on-board camera. +# parent: Solid Camera { #fields that inherit from the Solid node: diff --git a/resources/nodes/Charger.wrl b/resources/nodes/Charger.wrl index 13b1df22874..53670c8d38b 100644 --- a/resources/nodes/Charger.wrl +++ b/resources/nodes/Charger.wrl @@ -1,5 +1,6 @@ # The Charger node is used to model a special kind of battery charger for the robots. # When a robot gets close to a Charger, the robot's battery gets recharged. +# parent: Solid Charger { #fields that inherit from the Solid node: diff --git a/resources/nodes/Compass.wrl b/resources/nodes/Compass.wrl index 61997be077a..6b79161c369 100644 --- a/resources/nodes/Compass.wrl +++ b/resources/nodes/Compass.wrl @@ -1,5 +1,6 @@ # A Compass node can be used to simulate 1, 2 and 3-axis digital compasses. # It indicates the direction of the simulated magnetic north which is specified in the WorldInfo node. +# parent: Solid Compass { #fields that inherit from the Solid node: diff --git a/resources/nodes/Connector.wrl b/resources/nodes/Connector.wrl index 9478f7c57ba..289b431fe9b 100644 --- a/resources/nodes/Connector.wrl +++ b/resources/nodes/Connector.wrl @@ -1,6 +1,7 @@ # Connector nodes are used to simulate mechanical docking systems, or any other type of device that # can dynamically create a rigid link with a similar device. # The physical connection between two Connectors can be created and destroyed at run time by the robot controller program. +# parent: Solid Connector { #fields that inherit from the Solid node: diff --git a/resources/nodes/Display.wrl b/resources/nodes/Display.wrl index 8de01d2ba7c..af3b0e45cb5 100644 --- a/resources/nodes/Display.wrl +++ b/resources/nodes/Display.wrl @@ -4,6 +4,7 @@ # It can model an embedded screen or it can display any graphical # information such as graphs, text, robot trajectory, filtered camera # images and so on. +# parent: Solid Display { #fields that inherit from the Solid node: diff --git a/resources/nodes/DistanceSensor.wrl b/resources/nodes/DistanceSensor.wrl index 676feb95fe9..31be85f211a 100644 --- a/resources/nodes/DistanceSensor.wrl +++ b/resources/nodes/DistanceSensor.wrl @@ -1,6 +1,7 @@ # The DistanceSensor node can be used to model an ultrasound sonar, an infra-red sensor, # a single-ray laser or any type of device that measures the distance to objects. # To model a Lidar sensor, you should rather use a Lidar node. +# parent: Solid DistanceSensor { #fields that inherit from the Solid node: diff --git a/resources/nodes/Emitter.wrl b/resources/nodes/Emitter.wrl index 535ed39fe4e..afa0ebd6199 100644 --- a/resources/nodes/Emitter.wrl +++ b/resources/nodes/Emitter.wrl @@ -1,6 +1,7 @@ # The Emitter node is used to model a radio, or infra-red emitter. # It can be used to send data packets to Receiver nodes (onboard other robots). # An Emitter cannot receive data: bidirectional communication requires two Emitter/Receiver pairs. +# parent: Solid Emitter { #fields that inherit from the Solid node: diff --git a/resources/nodes/Fluid.wrl b/resources/nodes/Fluid.wrl index 8d426d8f0e1..a56c2535d02 100644 --- a/resources/nodes/Fluid.wrl +++ b/resources/nodes/Fluid.wrl @@ -1,4 +1,5 @@ # A Fluid node can be used to represent a collection of fluid volumes where hydrostatic and hydrodynamic forces apply. +# parent: Pose Fluid { #fields that inherit from the Pose node: diff --git a/resources/nodes/GPS.wrl b/resources/nodes/GPS.wrl index 8f0f46951a9..39bbea3702a 100644 --- a/resources/nodes/GPS.wrl +++ b/resources/nodes/GPS.wrl @@ -1,4 +1,5 @@ # The GPS node can be used to determine the global position of a robot or of a robot part. +# parent: Solid GPS { #fields that inherit from the Solid node: diff --git a/resources/nodes/Gyro.wrl b/resources/nodes/Gyro.wrl index c1f14f5a3e5..883db4d5282 100644 --- a/resources/nodes/Gyro.wrl +++ b/resources/nodes/Gyro.wrl @@ -1,5 +1,6 @@ # A Gyro node measures the angular velocity about 3 orthogonal axes (X, Y and Z). # The output is in rad/s. The Gyro node is mostly used for balance control. +# parent: Solid Gyro { #fields that inherit from the Solid node: diff --git a/resources/nodes/Hinge2Joint.wrl b/resources/nodes/Hinge2Joint.wrl index 6e87d208921..362a0ad6fbc 100644 --- a/resources/nodes/Hinge2Joint.wrl +++ b/resources/nodes/Hinge2Joint.wrl @@ -1,6 +1,7 @@ # A Hinge2Joint can be used to simulate a combination of two rotating motions along axes which intersect. # It is equivalent to two HingeJoint nodes but it spares the creation of an intermediate solid and is therefore more stable. # Spring and damping behavior can be specified. +# parent: HingeJoint Hinge2Joint { field SFNode jointParameters NULL # HingeJointParameters specifying anchor, axis, spring and damper constants, minStop, maxStop, suspension diff --git a/resources/nodes/HingeJointParameters.wrl b/resources/nodes/HingeJointParameters.wrl index c42c417c3a4..c6a0c87bbbb 100644 --- a/resources/nodes/HingeJointParameters.wrl +++ b/resources/nodes/HingeJointParameters.wrl @@ -1,4 +1,5 @@ # A HingeJointParameters defines the parameters of a HingeJoint node. +# parent: JointParameters HingeJointParameters { field SFFloat position 0 # current position (m or rad) diff --git a/resources/nodes/InertialUnit.wrl b/resources/nodes/InertialUnit.wrl index 02a9f91e9c0..7cc3aca09ac 100644 --- a/resources/nodes/InertialUnit.wrl +++ b/resources/nodes/InertialUnit.wrl @@ -1,6 +1,7 @@ # The InertialUnit node simulates an Inertial Measurement Unit (IMU). # The InertialUnit node computes and returns the roll, pitch and yaw angles of the # robot with respect to the global coordinate system defined in the WorldInfo node. +# parent: Solid InertialUnit { #fields that inherit from the Solid node: diff --git a/resources/nodes/LED.wrl b/resources/nodes/LED.wrl index f79000b5acd..3dfab69dbd8 100644 --- a/resources/nodes/LED.wrl +++ b/resources/nodes/LED.wrl @@ -1,4 +1,5 @@ # The LED node can be used to model a light emitting diode (LED) that can be controlled by the robot. +# parent: Solid LED { #fields that inherit from the Solid node: diff --git a/resources/nodes/Lidar.wrl b/resources/nodes/Lidar.wrl index f460f2a517e..6ee95adfdb5 100644 --- a/resources/nodes/Lidar.wrl +++ b/resources/nodes/Lidar.wrl @@ -1,5 +1,6 @@ # The Lidar node is used to model an on-board lidar. # A lidar is used to measure the distance to obstacles. +# parent: Solid Lidar { #fields that inherit from the Solid node: diff --git a/resources/nodes/LightSensor.wrl b/resources/nodes/LightSensor.wrl index 7451194410b..702e46c4eb5 100644 --- a/resources/nodes/LightSensor.wrl +++ b/resources/nodes/LightSensor.wrl @@ -1,6 +1,7 @@ # A LightSensor node can be used to model a phototransistor, a photodiode or any type # of device that measures the irradiance of light on its surface. # A LightSensor node detects the light emitted by PointLight, SpotLight and DirectionalLight nodes. +# parent: Solid LightSensor { # fields inherited from the Solid node: diff --git a/resources/nodes/Microphone.wrl b/resources/nodes/Microphone.wrl index a7a4df727f7..a17826356fa 100644 --- a/resources/nodes/Microphone.wrl +++ b/resources/nodes/Microphone.wrl @@ -1,3 +1,4 @@ +# parent: Solid Microphone { # fields that inherit from the Solid node: w3dField SFVec3f translation 0 0 0 diff --git a/resources/nodes/Pen.wrl b/resources/nodes/Pen.wrl index 025d66716f0..12f522ce40a 100644 --- a/resources/nodes/Pen.wrl +++ b/resources/nodes/Pen.wrl @@ -1,5 +1,6 @@ # A Pen node can be used to model a pen attached to a mobile robot. # It can draw the trajectory of the robot on a textured ground. +# parent: Solid Pen { #fields that inherit from the Solid node: diff --git a/resources/nodes/Pose.wrl b/resources/nodes/Pose.wrl index 66d61f806e0..7465b4d4bb3 100644 --- a/resources/nodes/Pose.wrl +++ b/resources/nodes/Pose.wrl @@ -1,5 +1,6 @@ # The Pose node is a grouping node that defines a coordinate system for its children that is # relative to the coordinate system of its parent. +# parent: Group Pose { #fields specific to the AbstractPose node: diff --git a/resources/nodes/PositionSensor.wrl b/resources/nodes/PositionSensor.wrl index 11b96bceb2e..07a7768d8a0 100644 --- a/resources/nodes/PositionSensor.wrl +++ b/resources/nodes/PositionSensor.wrl @@ -1,4 +1,5 @@ # A PositionSensor allows a robot controller to read the position of a joint with respect to its main axis. +# parent: Solid PositionSensor { field SFString name "position sensor" # used by wb_robot_get_device() diff --git a/resources/nodes/Propeller.wrl b/resources/nodes/Propeller.wrl index 7de1ddc67bb..2c21c71ca53 100644 --- a/resources/nodes/Propeller.wrl +++ b/resources/nodes/Propeller.wrl @@ -1,5 +1,6 @@ # The Propeller node is used to model a motorized helix propeller. # It can be used to propel underwater robots, flying robots, floating robots or even wheeled robots. +# parent: Solid Propeller { field SFVec3f shaftAxis 1 0 0 # thrust direction (m) diff --git a/resources/nodes/Radar.wrl b/resources/nodes/Radar.wrl index 1966c5509f7..d0c2d8908a4 100644 --- a/resources/nodes/Radar.wrl +++ b/resources/nodes/Radar.wrl @@ -1,4 +1,5 @@ # The Radar node is used to model a radar device, commonly found in automobiles. +# parent: Solid Radar { #fields that inherit from the Solid node: diff --git a/resources/nodes/Radio.wrl b/resources/nodes/Radio.wrl index 2daa9a566a0..b07fcc508b8 100644 --- a/resources/nodes/Radio.wrl +++ b/resources/nodes/Radio.wrl @@ -1,4 +1,5 @@ # Experimental Radio node. +# parent: Solid Radio { #fields that inherit from the Solid node: diff --git a/resources/nodes/RangeFinder.wrl b/resources/nodes/RangeFinder.wrl index 5651bb3bc9c..ae2620dddae 100644 --- a/resources/nodes/RangeFinder.wrl +++ b/resources/nodes/RangeFinder.wrl @@ -1,5 +1,6 @@ # The RangeFinder node is used to model an on-board range finder. # A range finder is used to measure the distance to obstacles. +# parent: Solid RangeFinder { #fields that inherit from the Solid node: diff --git a/resources/nodes/Receiver.wrl b/resources/nodes/Receiver.wrl index 381f5b81a58..713d83cf58c 100644 --- a/resources/nodes/Receiver.wrl +++ b/resources/nodes/Receiver.wrl @@ -1,6 +1,7 @@ # A Receiver node models a radio or infra-red receiver. # It can be used to receive data packets emitted by Emitter nodes (onboard other robots). # A Receiver cannot emit data: bidirectional communication requires two Emitter/Receiver pairs. +# parent: Solid Receiver { #fields that inherit from the Solid node: diff --git a/resources/nodes/Robot.wrl b/resources/nodes/Robot.wrl index 7a1ba009e4e..c971af28c0a 100644 --- a/resources/nodes/Robot.wrl +++ b/resources/nodes/Robot.wrl @@ -1,4 +1,5 @@ # The Robot node is a generic type of robot. +# parent: Solid Robot { #fields that inherit from the Solid node: diff --git a/resources/nodes/Solid.wrl b/resources/nodes/Solid.wrl index 332b50b4bef..45f1f7f05fc 100644 --- a/resources/nodes/Solid.wrl +++ b/resources/nodes/Solid.wrl @@ -1,6 +1,7 @@ # A Solid node can be used to represent objects in the simulated environment (e.g. obstacles, walls, ground, robot parts, etc.). # Solid nodes can be collision detected (boundingObject) and therefore can prevent objects from intersecting. # In addition, Solid nodes can have an optional Physics node that allow them to be simulated with the physics engine. +# parent: Pose Solid { #fields that inherit from the Pose node: diff --git a/resources/nodes/Speaker.wrl b/resources/nodes/Speaker.wrl index 0f925047b9a..31b82f98d65 100644 --- a/resources/nodes/Speaker.wrl +++ b/resources/nodes/Speaker.wrl @@ -1,6 +1,7 @@ # The Speaker node is used to model a loudspeaker device. # It can be used to playback wav sound files as well as text-to-speech. # The sounds are localized in the 3D space and rendered at the main viewpoint in stereo. +# parent: Solid Speaker { #fields that inherit from the Solid node: diff --git a/resources/nodes/TouchSensor.wrl b/resources/nodes/TouchSensor.wrl index d45a9e5ccec..810f20337bf 100644 --- a/resources/nodes/TouchSensor.wrl +++ b/resources/nodes/TouchSensor.wrl @@ -1,6 +1,7 @@ # A TouchSensor can be used to measure contact force ("force", "force-3d") or simply detect collisions ("bumper"). # It is critical that 'boundingObject' of a TouchSensor is defined and placed appropriately. # Refer to the Webots reference manual for more information on this. +# parent: Solid TouchSensor { #models a bumper, button, cat whisker, force sensor etc. #fields that inherit from the Solid node: diff --git a/resources/nodes/Track.wrl b/resources/nodes/Track.wrl index ccff593c54b..77a8f6c4b3f 100644 --- a/resources/nodes/Track.wrl +++ b/resources/nodes/Track.wrl @@ -1,4 +1,5 @@ # The Track node can be used to simulate tracks of tank robots or conveyor belts. +# parent: Solid Track { #fields that inherit from the Solid node: diff --git a/resources/nodes/TrackWheel.wrl b/resources/nodes/TrackWheel.wrl index 7422c3ad2cb..c927d9b130e 100644 --- a/resources/nodes/TrackWheel.wrl +++ b/resources/nodes/TrackWheel.wrl @@ -1,4 +1,5 @@ # A TrackWheel node can be used to simulate a wheel that it is part of a track system. +# parent: Group TrackWheel { #fields specific to the TrackWheel node: diff --git a/resources/nodes/Transform.wrl b/resources/nodes/Transform.wrl index ababcd3a51e..779ff528904 100644 --- a/resources/nodes/Transform.wrl +++ b/resources/nodes/Transform.wrl @@ -1,6 +1,7 @@ # The Transform node is a grouping node that defines a coordinate system for its children that is # relative to the coordinate system of its parent. # The 'scale' field of a Transform node can be adjusted only in a graphical context and not in a 'boundingObject' context. +# parent: Pose Transform { #fields specific to the AbstractTransform node: diff --git a/resources/nodes/VacuumGripper.wrl b/resources/nodes/VacuumGripper.wrl index 0d25e55475a..05950fe95d0 100644 --- a/resources/nodes/VacuumGripper.wrl +++ b/resources/nodes/VacuumGripper.wrl @@ -1,5 +1,6 @@ # VacuumGripper nodes are used to simulate vacuum suction systems. # The physical connection with a Solid can be created and destroyed at run time by the robot controller program. +# parent: Solid VacuumGripper { #fields that inherit from the Solid node: diff --git a/scripts/packaging/generate_proto_list.py b/scripts/packaging/generate_proto_list.py index 2d5baece90e..cd6796cb99e 100644 --- a/scripts/packaging/generate_proto_list.py +++ b/scripts/packaging/generate_proto_list.py @@ -33,6 +33,7 @@ def __init__(self, path, name): self.path = path.replace('\\', '/') # use cross-platform forward slashes self.proto_type = None # direct node type, ex: for RoadSegment is Road self.base_type = None # lowest node type, ex: for RoadSegment is Solid + self.parents = [] # all proto types this type extends, ex: for Road Segment is [Road] self.license = None self.license_url = None self.description = '' @@ -154,6 +155,7 @@ def generate_proto_list(current_tag=None, silent=False): if info.base_type not in protos: # the current proto depends on a sub-proto: iterate until a base node is reached raise RuntimeError(f'Error: "{info.base_type}" proto node does not exist. Either it was skipped or the regex ' 'that retrieves the proto_type is incorrect.') + info.parents.append(info.base_type) sub_proto = protos[info.base_type] info.base_type = sub_proto.proto_type @@ -201,6 +203,7 @@ def generate_proto_list(current_tag=None, silent=False): proto_element = ET.SubElement(root, 'proto') ET.SubElement(proto_element, 'name').text = info.name ET.SubElement(proto_element, 'base-type').text = info.base_type + ET.SubElement(proto_element, 'parents').text = ','.join(info.parents) ET.SubElement(proto_element, 'url').text = info.path.replace(WEBOTS_HOME + '/', prefix) if info.license is not None: diff --git a/src/webots/Makefile b/src/webots/Makefile index 59a153b33dd..45ec5f6c390 100644 --- a/src/webots/Makefile +++ b/src/webots/Makefile @@ -290,6 +290,7 @@ QT_SOURCES = WbAboutBox.cpp \ WbFieldIntSpinBox.cpp \ WbFieldLineEdit.cpp \ WbFindReplaceDialog.cpp \ + WbFieldValueRestriction.cpp \ WbFluid.cpp \ WbFocus.cpp \ WbFog.cpp \ diff --git a/src/webots/nodes/utils/WbDictionary.cpp b/src/webots/nodes/utils/WbDictionary.cpp index e10f8172589..d77fb3a84c0 100644 --- a/src/webots/nodes/utils/WbDictionary.cpp +++ b/src/webots/nodes/utils/WbDictionary.cpp @@ -120,8 +120,8 @@ bool WbDictionary::updateDef(WbBaseNode *&node, WbSFNode *sfNode, WbMFNode *mfNo definitionNode = static_cast(defNodes[defIndex]); QString error; assert(node->parentField() && node->parentNode()); - typeMatch = WbNodeUtilities::isAllowedToInsert(node->parentField(), definitionNode->nodeModelName(), node->parentNode(), - error, nodeUse, QString(), QStringList(definitionNode->nodeModelName())); + typeMatch = WbNodeUtilities::isAllowedToInsert(node->parentField(), node->parentNode(), error, nodeUse, QString(), + definitionNode); match = typeMatch && !definitionNode->isAnAncestorOf(node); } @@ -137,9 +137,8 @@ bool WbDictionary::updateDef(WbBaseNode *&node, WbSFNode *sfNode, WbMFNode *mfNo definitionNode = static_cast(matchingNode->defNode()); QString error; assert(node->parentField() && node->parentNode()); - typeMatch = - WbNodeUtilities::isAllowedToInsert(node->parentField(), definitionNode->nodeModelName(), node->parentNode(), error, - nodeUse, QString(), QStringList(definitionNode->nodeModelName())); + typeMatch = WbNodeUtilities::isAllowedToInsert(node->parentField(), node->parentNode(), error, nodeUse, QString(), + definitionNode); } } @@ -464,8 +463,7 @@ bool WbDictionary::checkBoundingObjectConstraints(const WbBaseNode *defNode, QSt if (sfnode) { const WbNode *const n = sfnode->value(); if (n) { - if (!WbNodeUtilities::isAllowedToInsert(fields[i], n->nodeModelName(), parentNode, errorMessage, nodeUse, QString(), - QStringList(n->nodeModelName()), false)) + if (!WbNodeUtilities::isAllowedToInsert(fields[i], parentNode, errorMessage, nodeUse, QString(), n, false)) return false; subNodes << n; } @@ -476,8 +474,7 @@ bool WbDictionary::checkBoundingObjectConstraints(const WbBaseNode *defNode, QSt for (int j = 0; j < size; ++j) { const WbNode *const n = mfnode->item(j); if (n) { - if (!WbNodeUtilities::isAllowedToInsert(fields[i], n->nodeModelName(), parentNode, errorMessage, nodeUse, - QString(), QStringList(n->nodeModelName()), false)) + if (!WbNodeUtilities::isAllowedToInsert(fields[i], parentNode, errorMessage, nodeUse, QString(), n, false)) return false; subNodes << n; @@ -535,8 +532,7 @@ bool WbDictionary::isSuitable(const WbNode *defNode, const QString &type) const assert(mTargetField); QString errorMessage; const WbNode::NodeUse targetNodeUse = static_cast(mTargetNode)->nodeUse(); - if (!WbNodeUtilities::isAllowedToInsert(mTargetField, defNode->nodeModelName(), mTargetNode, errorMessage, targetNodeUse, - type, QStringList(defNode->nodeModelName()), true)) + if (!WbNodeUtilities::isAllowedToInsert(mTargetField, mTargetNode, errorMessage, targetNodeUse, type, defNode, true)) return false; const WbBaseNode *defBaseNode = dynamic_cast(defNode); diff --git a/src/webots/nodes/utils/WbNodeOperations.cpp b/src/webots/nodes/utils/WbNodeOperations.cpp index c566770613a..4d20d1856fa 100644 --- a/src/webots/nodes/utils/WbNodeOperations.cpp +++ b/src/webots/nodes/utils/WbNodeOperations.cpp @@ -182,9 +182,8 @@ WbNodeOperations::OperationResult WbNodeOperations::importNode(WbNode *parentNod foreach (WbNode *node, nodes) { childNode = static_cast(node); QString errorMessage; - if (WbNodeUtilities::isAllowedToInsert(field, childNode->nodeModelName(), parentNode, errorMessage, nodeUse, - WbNodeUtilities::slotType(childNode), - QStringList() << childNode->nodeModelName() << childNode->modelName(), false)) { + if (WbNodeUtilities::isAllowedToInsert(field, parentNode, errorMessage, nodeUse, WbNodeUtilities::slotType(childNode), + childNode, false)) { if (avoidIntersections) tryToAvoidIntersections(childNode); const OperationResult result = initNewNode(childNode, parentNode, field, nodeIndex, true); diff --git a/src/webots/nodes/utils/WbNodeUtilities.cpp b/src/webots/nodes/utils/WbNodeUtilities.cpp index 27381ab913c..7efcfc78847 100644 --- a/src/webots/nodes/utils/WbNodeUtilities.cpp +++ b/src/webots/nodes/utils/WbNodeUtilities.cpp @@ -584,16 +584,12 @@ namespace { return dynamic_cast(node); } - bool doesFieldRestrictionAcceptNode(const WbField *const field, const QStringList &nodeNames) { + bool doesFieldRestrictionAcceptNode(const WbField *const field, const QString &nodeModelName, const WbNodeModel *nodeModel, + const QStringList &protoParentList) { assert(field->hasRestrictedValues()); - foreach (const WbVariant variant, field->acceptedValues()) { - if (variant.type() != WB_SF_NODE) - continue; - const WbNode *acceptedNode = variant.toNode(); - assert(acceptedNode); - if (nodeNames.contains(acceptedNode->modelName())) + foreach (const WbFieldValueRestriction restriction, field->acceptedValues()) + if (restriction.isNodeAccepted(nodeModelName, nodeModel, protoParentList)) return true; - } return false; } }; // namespace @@ -1501,29 +1497,32 @@ bool WbNodeUtilities::validateExistingChildNode(const WbField *const field, cons WbNodeUtilities::slotType(childNode)); } -bool WbNodeUtilities::isAllowedToInsert(const WbField *const field, const QString &nodeName, const WbNode *node, - QString &errorMessage, WbNode::NodeUse nodeUse, const QString &type, - const QStringList &restrictionValidNodeNames, bool automaticBoundingObjectCheck) { - if (field->hasRestrictedValues() && !doesFieldRestrictionAcceptNode(field, restrictionValidNodeNames)) +bool WbNodeUtilities::isAllowedToInsert(const WbField *const field, const WbNode *node, QString &errorMessage, + WbNode::NodeUse nodeUse, const QString &type, const QString &newNodeModelName, + const WbNodeModel *newNodeBaseModel, const QStringList &newNodeProtoParentList, + bool automaticBoundingObjectCheck) { + if (field->hasRestrictedValues() && + !doesFieldRestrictionAcceptNode(field, newNodeModelName, newNodeBaseModel, newNodeProtoParentList)) return false; if (field->isParameter()) { - bool valid = true; foreach (WbField *internalField, field->internalFields()) { + bool valid; if (internalField->isParameter()) // recursive call: check only node field names and not parameter names - valid = isAllowedToInsert(internalField, nodeName, internalField->parentNode(), errorMessage, WbNode::UNKNOWN_USE, type, - restrictionValidNodeNames, automaticBoundingObjectCheck); + valid = isAllowedToInsert(internalField, internalField->parentNode(), errorMessage, WbNode::UNKNOWN_USE, type, + newNodeModelName, newNodeBaseModel, newNodeProtoParentList, automaticBoundingObjectCheck); else { const WbNode *parentNode = internalField->parentNode(); - valid = ::isAllowedToInsert(internalField->name(), nodeName, parentNode, errorMessage, + valid = ::isAllowedToInsert(internalField->name(), newNodeBaseModel->name(), parentNode, errorMessage, static_cast(parentNode)->nodeUse(), type, automaticBoundingObjectCheck); } if (!valid) return false; } - return valid; + return true; } else - return ::isAllowedToInsert(field->name(), nodeName, node, errorMessage, nodeUse, type, automaticBoundingObjectCheck); + return ::isAllowedToInsert(field->name(), newNodeBaseModel->name(), node, errorMessage, nodeUse, type, + automaticBoundingObjectCheck); } WbNodeUtilities::Answer WbNodeUtilities::isSuitableForTransform(const WbNode *const srcNode, const QString &destModelName, diff --git a/src/webots/nodes/utils/WbNodeUtilities.hpp b/src/webots/nodes/utils/WbNodeUtilities.hpp index 87969f605f1..8acf21452bc 100644 --- a/src/webots/nodes/utils/WbNodeUtilities.hpp +++ b/src/webots/nodes/utils/WbNodeUtilities.hpp @@ -22,7 +22,9 @@ // #include "WbNode.hpp" +#include "WbNodeModel.hpp" #include "WbOdeTypes.hpp" +#include "WbProtoModel.hpp" #include @@ -167,13 +169,26 @@ namespace WbNodeUtilities { // return false if the Slot structure is invalid and insertion should be aborted bool validateInsertedNode(WbField *field, const WbNode *newNode, const WbNode *parentNode, bool isInBoundingObject); - // check if a node with node model 'modelName' can be inserted in the field 'field' of parent node 'node' + // check if a new node with the given parameters can be inserted in the field 'field' of parent node 'node' // in case of PROTO parent node and parameter field, // it first retrieve the base field and model and then check the validity // type is checked in case of Slot node - bool isAllowedToInsert(const WbField *const field, const QString &nodeName, const WbNode *node, QString &errorMessage, - WbNode::NodeUse nodeUse, const QString &type, const QStringList &restrictionValidNodeNames, - bool automaticBoundingObjectCheck = true); + bool isAllowedToInsert(const WbField *const field, const WbNode *node, QString &errorMessage, WbNode::NodeUse nodeUse, + const QString &type, const QString &newNodeModelName, const WbNodeModel *newNodeBaseModel, + const QStringList &newNodeProtoParentList, bool automaticBoundingObjectCheck = true); + inline bool isAllowedToInsert(const WbField *const field, const WbNode *node, QString &errorMessage, WbNode::NodeUse nodeUse, + const QString &type, const QString &newNodeBaseModelName, const QString &newNodeModelName, + const QStringList &newNodeProtoParentList, bool automaticBoundingObjectCheck = true) { + return isAllowedToInsert(field, node, errorMessage, nodeUse, type, newNodeModelName, + WbNodeModel::findModel(newNodeBaseModelName), newNodeProtoParentList, + automaticBoundingObjectCheck); + } + inline bool isAllowedToInsert(const WbField *const field, const WbNode *node, QString &errorMessage, WbNode::NodeUse nodeUse, + const QString &type, const WbNode *newNode, bool automaticBoundingObjectCheck = true) { + return isAllowedToInsert(field, node, errorMessage, nodeUse, type, newNode->modelName(), newNode->model(), + newNode->isProtoInstance() ? newNode->proto()->parentProtoNames() : QStringList(), + automaticBoundingObjectCheck); + } // check existing node structure bool validateExistingChildNode(const WbField *const field, const WbNode *childNode, const WbNode *node, diff --git a/src/webots/nodes/utils/WbWorld.cpp b/src/webots/nodes/utils/WbWorld.cpp index 2e12687f4d0..1864c0d33a1 100644 --- a/src/webots/nodes/utils/WbWorld.cpp +++ b/src/webots/nodes/utils/WbWorld.cpp @@ -135,8 +135,8 @@ WbWorld::WbWorld(WbTokenizer *tokenizer) : return; } QString errorMessage; - if (WbNodeUtilities::isAllowedToInsert(childrenField, node->nodeModelName(), mRoot, errorMessage, WbNode::STRUCTURE_USE, - WbNodeUtilities::slotType(node), QStringList(node->nodeModelName()))) { + if (WbNodeUtilities::isAllowedToInsert(childrenField, mRoot, errorMessage, WbNode::STRUCTURE_USE, + WbNodeUtilities::slotType(node), node)) { node->validate(); mRoot->addChild(node); } else diff --git a/src/webots/scene_tree/WbAddNodeDialog.cpp b/src/webots/scene_tree/WbAddNodeDialog.cpp index 7964bffd1b3..1855224a3f8 100644 --- a/src/webots/scene_tree/WbAddNodeDialog.cpp +++ b/src/webots/scene_tree/WbAddNodeDialog.cpp @@ -395,11 +395,9 @@ void WbAddNodeDialog::showNodeInfo(const QString &nodeFileName, NodeType nodeTyp setPixmap(pixmapPath); } -bool WbAddNodeDialog::doFieldRestrictionsAllowNode(const QString &nodeName) const { - foreach (const WbVariant variant, mField->acceptedValues()) { - const WbNode *node = variant.toNode(); - assert(node); - if (node->modelName() == nodeName) +bool WbAddNodeDialog::doFieldRestrictionsAllowNode(const WbNode *node) const { + foreach (const WbFieldValueRestriction restriction, mField->acceptedValues()) { + if (restriction.isNodeAccepted(node)) return true; } return false; @@ -437,8 +435,8 @@ void WbAddNodeDialog::buildTree() { QFileInfo fileInfo(basicNodeName); QString errorMessage; if (fileInfo.baseName().contains(regexp) && - WbNodeUtilities::isAllowedToInsert(mField, fileInfo.baseName(), mCurrentNode, errorMessage, nodeUse, QString(), - QStringList(fileInfo.baseName()))) { + WbNodeUtilities::isAllowedToInsert(mField, mCurrentNode, errorMessage, nodeUse, QString(), fileInfo.baseName(), + fileInfo.baseName(), QStringList())) { item = new QTreeWidgetItem(nodesItem, QStringList(fileInfo.baseName())); item->setIcon(0, QIcon("enabledIcons:node.png")); nodesItem->addChild(item); @@ -463,8 +461,7 @@ void WbAddNodeDialog::buildTree() { const QString ¤tFullDefName = currentDefName + " (" + currentModelName + ")"; if (!currentFullDefName.contains(regexp)) continue; - if (mField->hasRestrictedValues() && - (!doFieldRestrictionsAllowNode(currentModelName) && !doFieldRestrictionsAllowNode(node->nodeModelName()))) + if (mField->hasRestrictedValues() && !doFieldRestrictionsAllowNode(node)) continue; QString nodeFilePath(currentModelName); if (!WbNodeModel::isBaseModelName(currentModelName)) { @@ -561,8 +558,8 @@ int WbAddNodeDialog::addProtosFromProtoList(QTreeWidgetItem *parentItem, int typ QString errorMessage; const QString nodeName = it.key(); - if (!WbNodeUtilities::isAllowedToInsert(mField, baseType, mCurrentNode, errorMessage, nodeUse, info->slotType(), - QStringList() << baseType << nodeName)) + if (!WbNodeUtilities::isAllowedToInsert(mField, mCurrentNode, errorMessage, nodeUse, info->slotType(), baseType, nodeName, + info->parents())) continue; // keep track of unique local proto that may clash diff --git a/src/webots/scene_tree/WbAddNodeDialog.hpp b/src/webots/scene_tree/WbAddNodeDialog.hpp index c7f27e4cf81..5d642d73c02 100644 --- a/src/webots/scene_tree/WbAddNodeDialog.hpp +++ b/src/webots/scene_tree/WbAddNodeDialog.hpp @@ -88,7 +88,7 @@ private slots: int addProtos(QTreeWidgetItem *parentItem, const QStringList &protoList, const QString &dirPath, const QRegularExpression ®exp, const QDir &rootDirectory); void showNodeInfo(const QString &nodeFileName, NodeType nodeType, int variant = -1, const QString &boundingObjectInfo = ""); - bool doFieldRestrictionsAllowNode(const QString &nodeName) const; + bool doFieldRestrictionsAllowNode(const WbNode *node) const; bool isDeclarationConflicting(const QString &protoName, const QString &url); diff --git a/src/webots/scene_tree/WbSceneTree.cpp b/src/webots/scene_tree/WbSceneTree.cpp index dfc7d50af95..7e719c39013 100644 --- a/src/webots/scene_tree/WbSceneTree.cpp +++ b/src/webots/scene_tree/WbSceneTree.cpp @@ -1069,9 +1069,9 @@ bool WbSceneTree::isPasteAllowed() { const WbClipboard::WbClipboardNodeInfo *clipboardNodeInfo = mClipboard->nodeInfo(); const QString &nodeModelName = clipboardNodeInfo->nodeModelName; QString errorMessage; - if (!WbNodeUtilities::isAllowedToInsert(field, nodeModelName, parentNode, errorMessage, + if (!WbNodeUtilities::isAllowedToInsert(field, parentNode, errorMessage, static_cast(parentNode)->nodeUse(), clipboardNodeInfo->slotType, - QStringList() << nodeModelName << clipboardNodeInfo->modelName)) + nodeModelName, clipboardNodeInfo->modelName, clipboardNodeInfo->protoParentList)) return false; if (clipboardNodeInfo->hasADeviceDescendant) diff --git a/src/webots/scene_tree/WbValueEditor.cpp b/src/webots/scene_tree/WbValueEditor.cpp index 2c6eb660745..8c7df3af9f1 100644 --- a/src/webots/scene_tree/WbValueEditor.cpp +++ b/src/webots/scene_tree/WbValueEditor.cpp @@ -61,10 +61,10 @@ void WbValueEditor::edit(WbNode *node, WbField *field, int index) { mComboBox->clear(); if (mField->singleType() != WB_SF_NODE && mField->hasRestrictedValues()) { if (mField->singleType() != WB_SF_STRING) { - foreach (const WbVariant acceptedVariant, mField->acceptedValues()) + foreach (const WbFieldValueRestriction acceptedVariant, mField->acceptedValues()) mComboBox->addItem(acceptedVariant.toSimplifiedStringRepresentation()); } else { // In case of MF/SF_STRING we don't want to display the starting and ending '"' - foreach (const WbVariant acceptedVariant, mField->acceptedValues()) + foreach (const WbFieldValueRestriction acceptedVariant, mField->acceptedValues()) mComboBox->addItem(acceptedVariant.toSimplifiedStringRepresentation().chopped(1).remove(0, 1)); } connect(mComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(apply()), Qt::UniqueConnection); diff --git a/src/webots/user_commands/WbClipboard.cpp b/src/webots/user_commands/WbClipboard.cpp index df2ed8d560b..d699efeb8ad 100644 --- a/src/webots/user_commands/WbClipboard.cpp +++ b/src/webots/user_commands/WbClipboard.cpp @@ -17,6 +17,7 @@ #include "WbBaseNode.hpp" #include "WbDictionary.hpp" #include "WbNodeUtilities.hpp" +#include "WbProtoModel.hpp" #include "WbRgb.hpp" #include "WbRotation.hpp" #include "WbVector2.hpp" @@ -151,6 +152,7 @@ void WbClipboard::setNode(WbNode *n, bool persistent) { mNodeInfo = new WbClipboardNodeInfo(); mNodeInfo->modelName = n->modelName(); mNodeInfo->nodeModelName = n->nodeModelName(); + mNodeInfo->protoParentList = n->proto() ? n->proto()->parentProtoNames() : QStringList(); mNodeInfo->slotType = WbNodeUtilities::slotType(n); mNodeInfo->hasADeviceDescendant = WbNodeUtilities::hasADeviceDescendant(n, true); mNodeInfo->hasAConnectorDescendant = mNodeInfo->hasADeviceDescendant || WbNodeUtilities::hasADeviceDescendant(n, false); diff --git a/src/webots/user_commands/WbClipboard.hpp b/src/webots/user_commands/WbClipboard.hpp index 3b540a3f0af..f05645ba3e0 100644 --- a/src/webots/user_commands/WbClipboard.hpp +++ b/src/webots/user_commands/WbClipboard.hpp @@ -56,6 +56,7 @@ class WbClipboard : public WbVariant { struct WbClipboardNodeInfo { QString modelName; QString nodeModelName; + QStringList protoParentList; QString slotType; bool hasADeviceDescendant; bool hasAConnectorDescendant; diff --git a/src/webots/vrml/WbField.cpp b/src/webots/vrml/WbField.cpp index ddf6f0dfca1..300b6f2b459 100644 --- a/src/webots/vrml/WbField.cpp +++ b/src/webots/vrml/WbField.cpp @@ -166,8 +166,9 @@ void WbField::checkValueIsAccepted() { int refusedIndex; if (!mModel->isValueAccepted(mValue, &refusedIndex)) { QString acceptedValuesList = ""; - foreach (const WbVariant acceptedValue, mModel->acceptedValues()) - acceptedValuesList += acceptedValue.toSimplifiedStringRepresentation() + ", "; + foreach (const WbFieldValueRestriction acceptedValue, mModel->acceptedValues()) + acceptedValuesList += + acceptedValue.toSimplifiedStringRepresentation() + (acceptedValue.allowsSubtypes() ? "+" : "") + ", "; acceptedValuesList.chop(2); QString error; if (isSingle()) { diff --git a/src/webots/vrml/WbField.hpp b/src/webots/vrml/WbField.hpp index 016b21cbade..f10389ad227 100644 --- a/src/webots/vrml/WbField.hpp +++ b/src/webots/vrml/WbField.hpp @@ -107,7 +107,7 @@ class WbField : public QObject { // accepted values bool hasRestrictedValues() const { return mModel->hasRestrictedValues(); } - const QList acceptedValues() const { return mModel->acceptedValues(); } + const QList acceptedValues() const { return mModel->acceptedValues(); } // enable forwarding signals when the size of MF fields changes void listenToValueSizeChanges() const; diff --git a/src/webots/vrml/WbFieldModel.cpp b/src/webots/vrml/WbFieldModel.cpp index bfdb253c4f8..efcf1a5def1 100644 --- a/src/webots/vrml/WbFieldModel.cpp +++ b/src/webots/vrml/WbFieldModel.cpp @@ -102,11 +102,11 @@ WbFieldModel::WbFieldModel(WbTokenizer *tokenizer, const QString &worldPath) { defaultValueIsValid = false; WbMultipleValue *multipleValue = dynamic_cast(mDefaultValue); if (multipleValue) - mAcceptedValues << multipleValue->variantValue(refusedIndex); + mAcceptedValues << WbFieldValueRestriction(multipleValue->variantValue(refusedIndex), false); else { WbSingleValue *singleValue = dynamic_cast(mDefaultValue); assert(singleValue); - mAcceptedValues << singleValue->variantValue(); + mAcceptedValues << WbFieldValueRestriction(singleValue->variantValue(), false); } } if (!defaultValueIsValid) @@ -176,23 +176,29 @@ WbValue *WbFieldModel::createValueForVrmlType(const QString &type, WbTokenizer * return NULL; } -QList WbFieldModel::getAcceptedValues(const QString &type, WbTokenizer *tokenizer, const QString &worldPath) { - QList values; +QList WbFieldModel::getAcceptedValues(const QString &type, WbTokenizer *tokenizer, + const QString &worldPath) { + QList values; while (tokenizer->nextWord() != '}') { tokenizer->ungetToken(); + const WbSingleValue *singleValue = dynamic_cast(WbFieldModel::createValueForVrmlType(type, tokenizer, worldPath)); assert(singleValue); - WbVariant variant(singleValue->variantValue()); - if (type == "SFNode" && variant.toNode()) { + bool allowSubtypeMatch = tokenizer->peekWord() == '+'; + if (allowSubtypeMatch) + tokenizer->nextToken(); + + WbFieldValueRestriction restriction(singleValue->variantValue(), allowSubtypeMatch); + if (type == "SFNode" && restriction.toNode()) { // explicit copy of the node to be persistent. - WbNode *copy = variant.toNode()->cloneAndReferenceProtoInstance(); - variant.setNode(copy, true); - QObject::connect(&variant, &QObject::destroyed, copy, &QObject::deleteLater); + WbNode *copy = restriction.toNode()->cloneAndReferenceProtoInstance(); + restriction.setNode(copy, true); + QObject::connect(&restriction, &QObject::destroyed, copy, &QObject::deleteLater); } - values << variant; + values << restriction; delete singleValue; } return values; @@ -207,23 +213,10 @@ bool WbFieldModel::isValueAccepted(const WbValue *value, int *refusedIndex) cons if (multipleValue) { for (int i = 0; i < multipleValue->size(); ++i) { bool accepted = false; - if (type() == WB_MF_NODE) { - const WbMFNode *mfNode = static_cast(value); - assert(mfNode); - foreach (const WbVariant acceptedVariant, mAcceptedValues) { - const WbNode *nodeAccepted = acceptedVariant.toNode(); - if (nodeAccepted && (mfNode->item(i)->nodeModelName() == nodeAccepted->modelName() || - mfNode->item(i)->modelName() == nodeAccepted->modelName())) { - accepted = true; - break; - } - } - } else { - foreach (const WbVariant acceptedVariant, mAcceptedValues) { - if (multipleValue->variantValue(i) == acceptedVariant) { - accepted = true; - break; - } + foreach (const WbFieldValueRestriction acceptedVariant, mAcceptedValues) { + if (acceptedVariant.isVariantAccepted(multipleValue->variantValue(i))) { + accepted = true; + break; } } if (!accepted) { @@ -234,23 +227,13 @@ bool WbFieldModel::isValueAccepted(const WbValue *value, int *refusedIndex) cons return true; } else { assert(singleValue); - foreach (const WbVariant acceptedVariant, mAcceptedValues) { - if (type() == WB_SF_NODE) { - const WbSFNode *sfNode = static_cast(value); - assert(sfNode); - if (!sfNode->value()) - return true; - const WbNode *nodeAccepted = acceptedVariant.toNode(); - assert(nodeAccepted); - if (sfNode->value()->nodeModelName() == nodeAccepted->modelName() || - sfNode->value()->modelName() == nodeAccepted->modelName()) - return true; - } else if (singleValue->variantValue() == acceptedVariant) + foreach (const WbFieldValueRestriction acceptedVariant, mAcceptedValues) { + if (acceptedVariant.isVariantAccepted(singleValue->variantValue())) return true; } *refusedIndex = 0; + return false; } - return false; } bool WbFieldModel::isMultiple() const { diff --git a/src/webots/vrml/WbFieldModel.hpp b/src/webots/vrml/WbFieldModel.hpp index 907a9619404..f2563acd94c 100644 --- a/src/webots/vrml/WbFieldModel.hpp +++ b/src/webots/vrml/WbFieldModel.hpp @@ -21,6 +21,9 @@ // #include +#include +#include +#include #include #include @@ -54,7 +57,7 @@ class WbFieldModel { // accepted values bool isValueAccepted(const WbValue *value, int *refusedIndex) const; bool hasRestrictedValues() const { return !mAcceptedValues.isEmpty(); } - const QList acceptedValues() const { return mAcceptedValues; } + const QList acceptedValues() const { return mAcceptedValues; } // field type WbFieldType type() const { return mDefaultValue->type(); } @@ -89,13 +92,14 @@ class WbFieldModel { bool mIsDeprecated; bool mIsUnconnected; WbValue *mDefaultValue; - QList mAcceptedValues; // TODO: const WbVariant + QList mAcceptedValues; // TODO: const WbVariant WbToken *mNameToken; mutable int mRefCount; static WbValue *createValueForVrmlType(const QString &type, WbTokenizer *tokenizer, const QString &worldPath); - static QList getAcceptedValues(const QString &type, WbTokenizer *tokenizer, const QString &worldPath); + static QList getAcceptedValues(const QString &type, WbTokenizer *tokenizer, + const QString &worldPath); }; #endif diff --git a/src/webots/vrml/WbFieldValueRestriction.cpp b/src/webots/vrml/WbFieldValueRestriction.cpp new file mode 100644 index 00000000000..b08636634f0 --- /dev/null +++ b/src/webots/vrml/WbFieldValueRestriction.cpp @@ -0,0 +1,68 @@ +// Copyright 1996-2023 Cyberbotics Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "WbFieldValueRestriction.hpp" + +#include "WbNode.hpp" + +WbFieldValueRestriction &WbFieldValueRestriction::operator=(const WbFieldValueRestriction &other) { + WbVariant::operator=(other); + mAllowsSubtypes = other.allowsSubtypes(); + return *this; +} + +bool WbFieldValueRestriction::operator==(const WbFieldValueRestriction &other) const { + return WbVariant::operator==(other) && allowsSubtypes() == other.allowsSubtypes(); +} + +bool WbFieldValueRestriction::operator!=(const WbFieldValueRestriction &other) const { + return !(*this == other); +} + +bool WbFieldValueRestriction::isVariantAccepted(const WbVariant &variant) const { + if (type() != variant.type()) + return false; + if (type() != WB_SF_NODE) + return variant == *this; + return isNodeAccepted(variant.toNode()); +} + +bool WbFieldValueRestriction::isNodeAccepted(const QString &nodeModelName, const WbNodeModel *nodeModel, + const QStringList &protoParentList) const { + if (type() != WB_SF_NODE || !toNode() || !nodeModel) + return false; + return nodeModelName == toNode()->modelName() || isBaseNodeTypeAccepted(nodeModel) || + (allowsSubtypes() && protoParentList.contains(toNode()->modelName())); +} + +bool WbFieldValueRestriction::isNodeAccepted(const WbNode *node) const { + if (type() != WB_SF_NODE) + return false; + if (!toNode() || !node) + return toNode() == node; + return toNode()->isProtoInstance() ? isProtoNodeTypeAccepted(node->proto()) : isBaseNodeTypeAccepted(node->model()); +} + +bool WbFieldValueRestriction::isBaseNodeTypeAccepted(const WbNodeModel *actualType) const { + if (type() != WB_SF_NODE || !toNode() || !actualType) + return false; + return toNode()->modelName() == actualType->name() || (allowsSubtypes() && isBaseNodeTypeAccepted(actualType->parentModel())); +} + +bool WbFieldValueRestriction::isProtoNodeTypeAccepted(const WbProtoModel *actualType) const { + if (type() != WB_SF_NODE || !toNode() || !actualType) + return false; + return toNode()->modelName() == actualType->name() || + (allowsSubtypes() && isProtoNodeTypeAccepted(actualType->ancestorProtoModel())); +} diff --git a/src/webots/vrml/WbFieldValueRestriction.hpp b/src/webots/vrml/WbFieldValueRestriction.hpp new file mode 100644 index 00000000000..c9b3cef017d --- /dev/null +++ b/src/webots/vrml/WbFieldValueRestriction.hpp @@ -0,0 +1,62 @@ +// Copyright 1996-2023 Cyberbotics Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef WB_FIELD_VALUE_RESTRICTION_HPP +#define WB_FIELD_VALUE_RESTRICTION_HPP + +#include +#include +#include +#include + +#include "../../../include/controller/c/webots/supervisor.h" + +class WbFieldValueRestriction : public WbVariant { + Q_OBJECT; + +public: + WbFieldValueRestriction() : WbVariant(), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(bool b) : WbVariant(b), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(int i) : WbVariant(i), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(double d) : WbVariant(d), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(const QString &s) : WbVariant(s), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(const WbVector2 &v) : WbVariant(v), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(const WbVector3 &v) : WbVariant(v), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(const WbRgb &c) : WbVariant(c), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(const WbRotation &r) : WbVariant(r), mAllowsSubtypes(false) {} + explicit WbFieldValueRestriction(WbNode *n, bool allowsSubtypes) : WbVariant(n), mAllowsSubtypes(allowsSubtypes) {} + explicit WbFieldValueRestriction(const WbVariant &variant, bool allowsSubtypes) : + WbVariant(variant), + mAllowsSubtypes(allowsSubtypes && variant.type() == WB_SF_NODE) {} + WbFieldValueRestriction &operator=(const WbFieldValueRestriction &other); + bool operator==(const WbFieldValueRestriction &other) const; + bool operator!=(const WbFieldValueRestriction &other) const; + + virtual ~WbFieldValueRestriction() {} + + const bool allowsSubtypes() const { return mAllowsSubtypes; } + + bool isVariantAccepted(const WbVariant &variant) const; + bool isNodeAccepted(const QString &nodeModelName, const WbNodeModel *nodeModel, const QStringList &protoParentList) const; + bool isNodeAccepted(const WbNode *node) const; + bool isBaseNodeTypeAccepted(const WbNodeModel *actualType) const; + // Note: This does not check if the proto's base type would be accepted by isBaseNodeTypeAccepted + bool isProtoNodeTypeAccepted(const WbProtoModel *actualType) const; + +private: + // If the restriction is a node type, whether the node type can be a model which "extends" the requested type + bool mAllowsSubtypes; +}; + +#endif diff --git a/src/webots/vrml/WbNodeModel.cpp b/src/webots/vrml/WbNodeModel.cpp index c8aabe7e287..e55bef727a0 100644 --- a/src/webots/vrml/WbNodeModel.cpp +++ b/src/webots/vrml/WbNodeModel.cpp @@ -34,7 +34,11 @@ void WbNodeModel::cleanup() { delete it.next().value(); } -WbNodeModel::WbNodeModel(WbTokenizer *tokenizer) : mInfo(tokenizer->info()), mName(tokenizer->nextWord()) { +WbNodeModel::WbNodeModel(WbTokenizer *tokenizer) : + mInfo(tokenizer->info()), + mName(tokenizer->nextWord()), + mParentName(tokenizer->parent()), + mParentModel(NULL) { tokenizer->skipToken("{"); while (tokenizer->peekWord() != "}") { @@ -78,6 +82,13 @@ void WbNodeModel::readAllModels() { cModels.insert(model->name(), model); } + // Now that all the models are loaded, populate the ancestry tree + foreach (QString baseModelName, baseModelNames()) { + WbNodeModel *baseModel = findModel(baseModelName); + if (baseModel) + baseModel->mParentModel = findModel(baseModel->mParentName); + } + qAddPostRoutine(WbNodeModel::cleanup); } diff --git a/src/webots/vrml/WbNodeModel.hpp b/src/webots/vrml/WbNodeModel.hpp index a2ee4a4fac1..10c9b582093 100644 --- a/src/webots/vrml/WbNodeModel.hpp +++ b/src/webots/vrml/WbNodeModel.hpp @@ -51,6 +51,11 @@ class WbNodeModel { const QList &fieldModels() const { return mFieldModels; } QStringList fieldNames(); + // parent nodes (only defined for base nodes. For proto nodes, see + // WbProtoModel::ancestorProtoName/ancestorProtoNameModel/baseType) + const QString parentName() const { return mParentName; }; + const WbNodeModel *parentModel() const { return mParentModel; }; + QStringList documentationBookAndPage() const { return QStringList() << "reference" << mName.toLower(); } private: @@ -60,6 +65,8 @@ class WbNodeModel { QString mInfo; QString mName; QList mFieldModels; + QString mParentName; + WbNodeModel *mParentModel; static WbNodeModel *readModel(const QString &fileName); static void readAllModels(); diff --git a/src/webots/vrml/WbParser.cpp b/src/webots/vrml/WbParser.cpp index 19b7ca7cab4..d2ffc32b5dd 100644 --- a/src/webots/vrml/WbParser.cpp +++ b/src/webots/vrml/WbParser.cpp @@ -151,6 +151,8 @@ void WbParser::parseFieldAcceptedValues(WbFieldType type, const QString &worldPa while (nextWord() != '}') { mTokenizer->ungetToken(); WbParser::parseSingleFieldValue(type, worldPath); + if (nextWord() != '+') + mTokenizer->ungetToken(); } } diff --git a/src/webots/vrml/WbProtoManager.cpp b/src/webots/vrml/WbProtoManager.cpp index 9d7676a50c5..1a8240b732d 100644 --- a/src/webots/vrml/WbProtoManager.cpp +++ b/src/webots/vrml/WbProtoManager.cpp @@ -436,7 +436,7 @@ void WbProtoManager::loadWebotsProtoMap() { if (reader.name().toString() == "proto") { bool needsRobotAncestor = false; QString name, url, baseType, license, licenseUrl, documentationUrl, description, slotType; - QStringList tags, parameters; + QStringList tags, parameters, parents; while (reader.readNextStartElement()) { if (reader.name().toString() == "name") { name = reader.readElementText(); @@ -478,6 +478,10 @@ void WbProtoManager::loadWebotsProtoMap() { parameters = reader.readElementText().split("\\n", Qt::SkipEmptyParts); reader.readNext(); } + if (reader.name().toString() == "parents") { + parents = reader.readElementText().split(",", Qt::SkipEmptyParts); + reader.readNext(); + } if (reader.name().toString() == "needs-robot-ancestor") { needsRobotAncestor = reader.readElementText() == "true"; reader.readNext(); @@ -485,7 +489,7 @@ void WbProtoManager::loadWebotsProtoMap() { } description = description.replace("\\n", "\n"); WbProtoInfo *const info = new WbProtoInfo(url, baseType, license, licenseUrl, documentationUrl, description, slotType, - tags, parameters, needsRobotAncestor); + tags, parameters, parents, needsRobotAncestor); mWebotsProtoList.insert(name, info); } else reader.raiseError(tr("Expected 'proto' element.")); @@ -756,9 +760,12 @@ WbProtoInfo *WbProtoManager::generateInfoFromProtoFile(const QString &protoFileN parameters << field; } + // generate parents string (needed by PROTO wizard) + QStringList parents = protoModel->parentProtoNames(); + WbProtoInfo *info = new WbProtoInfo(url, protoModel->baseType(), protoModel->license(), protoModel->licenseUrl(), protoModel->documentationUrl(), protoModel->info(), protoModel->slotType(), - protoModel->tags(), parameters, needsRobotAncestor); + protoModel->tags(), parameters, parents, needsRobotAncestor); protoModel->destroy(); return info; diff --git a/src/webots/vrml/WbProtoManager.hpp b/src/webots/vrml/WbProtoManager.hpp index 6c2073d7437..50f0b89e260 100644 --- a/src/webots/vrml/WbProtoManager.hpp +++ b/src/webots/vrml/WbProtoManager.hpp @@ -56,7 +56,7 @@ class WbProtoInfo { public: WbProtoInfo(const QString &url, const QString &baseType, const QString &license, const QString &licenseUrl, const QString &documentationUrl, const QString &description, const QString &slotType, const QStringList &tags, - const QStringList ¶meters, bool needsRobotAncestor) : + const QStringList ¶meters, const QStringList &parents, bool needsRobotAncestor) : mUrl(url), mBaseType(baseType), mLicense(license), @@ -66,6 +66,7 @@ class WbProtoInfo { mSlotType(slotType), mTags(tags), mParameters(parameters), + mParents(parents), mNeedsRobotAncestor(needsRobotAncestor), mIsDirty(false) { // extract parameter names @@ -81,7 +82,7 @@ class WbProtoInfo { // copy constructor WbProtoInfo(const WbProtoInfo &other) : WbProtoInfo(other.mUrl, other.mBaseType, other.mLicense, other.mLicenseUrl, other.mDocumentationUrl, other.mDescription, - other.mSlotType, other.mTags, other.mParameters, other.mNeedsRobotAncestor) {} + other.mSlotType, other.mTags, other.mParameters, other.mParents, other.mNeedsRobotAncestor) {} const QString &url() const { return mUrl; } const QString &baseType() const { return mBaseType; } @@ -93,6 +94,7 @@ class WbProtoInfo { const QStringList &tags() const { return mTags; } const QStringList ¶meters() const { return mParameters; } const QStringList ¶meterNames() const { return mParameterNames; } + const QStringList &parents() const { return mParents; } const bool needsRobotAncestor() const { return mNeedsRobotAncestor; } void setDirty(bool value) { mIsDirty = value; } bool isDirty() const { return mIsDirty; } @@ -107,6 +109,7 @@ class WbProtoInfo { QString mSlotType; QStringList mTags; QStringList mParameters; + QStringList mParents; bool mNeedsRobotAncestor; bool mIsDirty; diff --git a/src/webots/vrml/WbProtoModel.cpp b/src/webots/vrml/WbProtoModel.cpp index b78fa5e7834..ae3e81dad0e 100644 --- a/src/webots/vrml/WbProtoModel.cpp +++ b/src/webots/vrml/WbProtoModel.cpp @@ -417,6 +417,14 @@ WbNode *WbProtoModel::generateRoot(const QVector ¶meters, const Q return root; } +QStringList WbProtoModel::parentProtoNames() const { + QStringList parents; + const WbProtoModel *parentProtoModel = this; + while ((parentProtoModel = parentProtoModel->ancestorProtoModel())) + parents << parentProtoModel->name(); + return parents; +} + void WbProtoModel::ref(bool isFromProtoInstanceCreation) { mRefCount++; if (isFromProtoInstanceCreation) diff --git a/src/webots/vrml/WbProtoModel.hpp b/src/webots/vrml/WbProtoModel.hpp index dccf030bd97..f6de9c06d1a 100644 --- a/src/webots/vrml/WbProtoModel.hpp +++ b/src/webots/vrml/WbProtoModel.hpp @@ -97,6 +97,8 @@ class WbProtoModel : public QObject { const QString &slotType() const { return mSlotType; } + QStringList parentProtoNames() const; + QStringList parameterNames() const; void setIsTemplate(bool value); diff --git a/src/webots/vrml/WbToken.cpp b/src/webots/vrml/WbToken.cpp index 4de9af6af30..cdfe629b760 100644 --- a/src/webots/vrml/WbToken.cpp +++ b/src/webots/vrml/WbToken.cpp @@ -39,7 +39,8 @@ WbToken::WbToken(const QString &word, int line, int column) : mLine(line), mColu bool ok; // cppcheck-suppress ignoredReturnValue word.toDouble(&ok); - mType = ok ? NUMERIC : INVALID; + // "+" on its own is a punctuation mark + mType = ok ? NUMERIC : word == "+" ? PUNCTUATION : INVALID; } else if (isKeyword(word)) mType = KEYWORD; else if (isValidIdentifier(word)) diff --git a/src/webots/vrml/WbTokenizer.cpp b/src/webots/vrml/WbTokenizer.cpp index 8b35fc9096e..8685c2ddffc 100644 --- a/src/webots/vrml/WbTokenizer.cpp +++ b/src/webots/vrml/WbTokenizer.cpp @@ -529,6 +529,17 @@ const QString WbTokenizer::documentationUrl() const { return QString(); } +const QString WbTokenizer::parent() const { + const QStringList lines = mInfo.split("\n"); + foreach (QString line, lines) { + if (line.startsWith("parent:")) { + line.remove("parent:"); + return line.trimmed(); + } + } + return QString(); +} + void WbTokenizer::reportError(const QString &message, int line, int column) const { const QString prefix = mFileName.isEmpty() ? mReferralFile : mFileName; if (prefix.isEmpty()) diff --git a/src/webots/vrml/WbTokenizer.hpp b/src/webots/vrml/WbTokenizer.hpp index e5b20145c3c..ee67e6446d7 100644 --- a/src/webots/vrml/WbTokenizer.hpp +++ b/src/webots/vrml/WbTokenizer.hpp @@ -61,6 +61,9 @@ class WbTokenizer { // returns the documentation URL stored as (# license url: string) comments in the file header const QString documentationUrl() const; + // returns the parent node type stored as (# parent: string) comments in the file header + const QString parent() const; + // returns file version found in file header const WbVersion &fileVersion() const { return mFileVersion; } diff --git a/tests/manual_tests/protos/DerivedPoseProto.proto b/tests/manual_tests/protos/DerivedPoseProto.proto new file mode 100644 index 00000000000..7d95678e36f --- /dev/null +++ b/tests/manual_tests/protos/DerivedPoseProto.proto @@ -0,0 +1,14 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/manual_tests/protos/PoseProto.proto" + +PROTO DerivedPoseProto [ + field SFVec3f translation 0 0 0 + field SFRotation rotation 0 0 1 0 +] +{ + PoseProto { + translation IS translation + rotation IS rotation + } +} diff --git a/tests/manual_tests/protos/PoseProto.proto b/tests/manual_tests/protos/PoseProto.proto new file mode 100644 index 00000000000..4900e1cd392 --- /dev/null +++ b/tests/manual_tests/protos/PoseProto.proto @@ -0,0 +1,12 @@ +#VRML_SIM R2024a utf8 + +PROTO PoseProto [ + field SFVec3f translation 0 0 0 + field SFRotation rotation 0 0 1 0 +] +{ + Pose { + translation IS translation + rotation IS rotation + } +} diff --git a/tests/parser/expected_results.txt b/tests/parser/expected_results.txt index 4a014a4fab4..b98c5349577 100644 --- a/tests/parser/expected_results.txt +++ b/tests/parser/expected_results.txt @@ -17,6 +17,7 @@ parser/worlds/missing_use_value.wbt "Expected field name or '}', found 'USE'." parser/worlds/nested_def_name.wbt VOID parser/worlds/nested_proto_missing_ending_curly_bracket.wbt "Expected parameter name or '}', found end of file." parser/worlds/node_without_brackets.wbt "Expected '{', found 'RectangleArena'." +parser/worlds/proto_allowed_mf_field_value.wbt VOID parser/worlds/proto_concatenated_comment.wbt VOID parser/worlds/proto_default_def_parameter.wbt "Wrong order of fields in the instance of PROTO DefaultDef: USE nodes might refer to the wrong DEF nodes." parser/worlds/proto_mismatch_field_type.wbt "Type mismatch between 'fieldTexture' PROTO parameter and field 'url'." @@ -25,7 +26,11 @@ parser/worlds/proto_missing_ending_curly_bracket.wbt "Expected field name or '}' parser/worlds/proto_missing_item_value_in_field.wbt "Expected floating point value, found 'field'." parser/worlds/proto_nested_parameter.wbt VOID parser/worlds/proto_not_allowed_field_value.wbt "Invalid 'translation' changed to 0 0 0. The value should be in the list: {0 0 0}." -parser/worlds/proto_not_allowed_mf_field_value.wbt "Invalid 'Pose' removed from 'extensionSlot' field. The values should be in the list: {Solid, Group}." +parser/worlds/proto_not_allowed_mf_field_value_base.wbt "Invalid 'Pose' removed from 'extensionSlot' field. The values should be in the list: {Solid+, PoseProto+}." +parser/worlds/proto_not_allowed_mf_field_value_base_no_subtypes.wbt "Invalid 'Solid' removed from 'extensionSlot3' field. The values should be in the list: {Pose}." +parser/worlds/proto_not_allowed_mf_field_value_base_of_proto.wbt "Invalid 'Pose' removed from 'extensionSlot2' field. The values should be in the list: {PoseProto}." +parser/worlds/proto_not_allowed_mf_field_value_proto.wbt "Invalid 'Parameter' removed from 'extensionSlot' field. The values should be in the list: {Solid+, PoseProto+}." +parser/worlds/proto_not_allowed_mf_field_value_proto_no_subtypes.wbt "Invalid 'DerivedPoseProto' removed from 'extensionSlot2' field. The values should be in the list: {PoseProto}." parser/worlds/proto_parameter_missing_ending_curly_bracket.wbt "Expected field name or '}', found ']'." parser/worlds/proto_typo_in_alias.wbt "PROTO parameter 'rotation' has no matching IS field." parser/worlds/proto_typo_in_is_keyword.wbt "Expected floating point value, found 'ISNOT'." diff --git a/tests/parser/protos/ProtoRestrictedFieldValues.proto b/tests/parser/protos/ProtoRestrictedFieldValues.proto index 6d4fd73b9b2..08bc8af0836 100644 --- a/tests/parser/protos/ProtoRestrictedFieldValues.proto +++ b/tests/parser/protos/ProtoRestrictedFieldValues.proto @@ -1,15 +1,29 @@ #VRML_SIM R2024a utf8 +EXTERNPROTO "webots://tests/manual_tests/protos/PoseProto.proto" + PROTO ProtoRestrictedFieldValues [ field SFRotation{0 1 0 0, 0 0 1 0, 0 1 0 1.5708} rotation 0 0 1 0 field SFVec3f{0 0 0} translation 0 0 0 - field MFNode{Solid{}, Group{}} extensionSlot [] + field MFNode{Solid{}+, PoseProto{}+} extensionSlot [] + field MFNode{PoseProto{}} extensionSlot2 [] + field MFNode{Pose{}} extensionSlot3 [] ] { Solid { rotation IS rotation translation IS translation - children IS extensionSlot + children [ + Group { + children IS extensionSlot + } + Group { + children IS extensionSlot2 + } + Group { + children IS extensionSlot3 + } + ] } } diff --git a/tests/parser/worlds/proto_allowed_mf_field_value.wbt b/tests/parser/worlds/proto_allowed_mf_field_value.wbt new file mode 100644 index 00000000000..7a72a77f2f6 --- /dev/null +++ b/tests/parser/worlds/proto_allowed_mf_field_value.wbt @@ -0,0 +1,49 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/manual_tests/protos/DerivedPoseProto.proto" +EXTERNPROTO "webots://tests/manual_tests/protos/PoseProto.proto" +EXTERNPROTO "webots://tests/parser/protos/ParserTestSupervisor.proto" +EXTERNPROTO "webots://projects/objects/floors/protos/Floor.proto" +EXTERNPROTO "webots://tests/parser/protos/ProtoRestrictedFieldValues.proto" + +WorldInfo { +} +Viewpoint { + orientation 0.7470290283624516 0.646616024550071 0.15438700586161153 5.42889 + position -1.2077 1.6424 1.96945 +} +Background { + skyColor [ + 0.4 0.7 1 + ] +} +ParserTestSupervisor { +} +DirectionalLight { + ambientIntensity 1 + direction -0.33 -1 -0.5 + castShadows TRUE +} +Floor { + rotation 1 0 0 -1.5708 +} +ProtoRestrictedFieldValues { + rotation 0 1 0 1.5708 + translation 0 0 0 + extensionSlot [ + DerivedPoseProto { + } + Solid { + } + Robot { + } + ] + extensionSlot2 [ + PoseProto { + } + ] + extensionSlot3 [ + DerivedPoseProto { + } + ] +} diff --git a/tests/parser/worlds/proto_not_allowed_field_value.wbt b/tests/parser/worlds/proto_not_allowed_field_value.wbt index 0d03bcef442..13da759e008 100644 --- a/tests/parser/worlds/proto_not_allowed_field_value.wbt +++ b/tests/parser/worlds/proto_not_allowed_field_value.wbt @@ -30,15 +30,13 @@ ProtoRestrictedFieldValues { rotation 0 1 0 1.5708 translation 0 1 0 extensionSlot [ - Group { + Solid { children [ Solid { name "solid(0)" } ] } - Solid { - } SolidBox { translation 0 0 2.2799999999999985 } diff --git a/tests/parser/worlds/proto_not_allowed_mf_field_value_base.wbt b/tests/parser/worlds/proto_not_allowed_mf_field_value_base.wbt new file mode 100644 index 00000000000..b10621b1ecb --- /dev/null +++ b/tests/parser/worlds/proto_not_allowed_mf_field_value_base.wbt @@ -0,0 +1,40 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/manual_tests/protos/DerivedPoseProto.proto" +EXTERNPROTO "webots://tests/parser/protos/ParserTestSupervisor.proto" +EXTERNPROTO "webots://projects/objects/floors/protos/Floor.proto" +EXTERNPROTO "webots://tests/parser/protos/ProtoRestrictedFieldValues.proto" + +WorldInfo { +} +Viewpoint { + orientation 0.7470290283624516 0.646616024550071 0.15438700586161153 5.42889 + position -1.2077 1.6424 1.96945 +} +Background { + skyColor [ + 0.4 0.7 1 + ] +} +ParserTestSupervisor { +} +DirectionalLight { + ambientIntensity 1 + direction -0.33 -1 -0.5 + castShadows TRUE +} +Floor { + rotation 1 0 0 -1.5708 +} +ProtoRestrictedFieldValues { + rotation 0 1 0 1.5708 + translation 0 0 0 + extensionSlot [ + DerivedPoseProto { + } + Pose { + } + Solid { + } + ] +} diff --git a/tests/parser/worlds/proto_not_allowed_mf_field_value_base_no_subtypes.wbt b/tests/parser/worlds/proto_not_allowed_mf_field_value_base_no_subtypes.wbt new file mode 100644 index 00000000000..328aa69dbdd --- /dev/null +++ b/tests/parser/worlds/proto_not_allowed_mf_field_value_base_no_subtypes.wbt @@ -0,0 +1,42 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/manual_tests/protos/DerivedPoseProto.proto" +EXTERNPROTO "webots://tests/parser/protos/ParserTestSupervisor.proto" +EXTERNPROTO "webots://projects/objects/floors/protos/Floor.proto" +EXTERNPROTO "webots://tests/parser/protos/ProtoRestrictedFieldValues.proto" + +WorldInfo { +} +Viewpoint { + orientation 0.7470290283624516 0.646616024550071 0.15438700586161153 5.42889 + position -1.2077 1.6424 1.96945 +} +Background { + skyColor [ + 0.4 0.7 1 + ] +} +ParserTestSupervisor { +} +DirectionalLight { + ambientIntensity 1 + direction -0.33 -1 -0.5 + castShadows TRUE +} +Floor { + rotation 1 0 0 -1.5708 +} +ProtoRestrictedFieldValues { + rotation 0 1 0 1.5708 + translation 0 0 0 + extensionSlot [ + DerivedPoseProto { + } + Solid { + } + ], + extensionSlot3 [ + Solid { + } + ] +} diff --git a/tests/parser/worlds/proto_not_allowed_mf_field_value.wbt b/tests/parser/worlds/proto_not_allowed_mf_field_value_base_of_proto.wbt similarity index 92% rename from tests/parser/worlds/proto_not_allowed_mf_field_value.wbt rename to tests/parser/worlds/proto_not_allowed_mf_field_value_base_of_proto.wbt index 89521758b23..2411cfbc3df 100644 --- a/tests/parser/worlds/proto_not_allowed_mf_field_value.wbt +++ b/tests/parser/worlds/proto_not_allowed_mf_field_value_base_of_proto.wbt @@ -29,11 +29,13 @@ ProtoRestrictedFieldValues { rotation 0 1 0 1.5708 translation 0 0 0 extensionSlot [ - Group { - } - Transform { + DerivedPoseProto { } Solid { } ] + extensionSlot2 [ + Pose { + } + ] } diff --git a/tests/parser/worlds/proto_not_allowed_mf_field_value_proto.wbt b/tests/parser/worlds/proto_not_allowed_mf_field_value_proto.wbt new file mode 100644 index 00000000000..d0c3df1b4c2 --- /dev/null +++ b/tests/parser/worlds/proto_not_allowed_mf_field_value_proto.wbt @@ -0,0 +1,41 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/manual_tests/protos/DerivedPoseProto.proto" +EXTERNPROTO "webots://tests/manual_tests/protos/Parameter.proto" +EXTERNPROTO "webots://tests/parser/protos/ParserTestSupervisor.proto" +EXTERNPROTO "webots://projects/objects/floors/protos/Floor.proto" +EXTERNPROTO "webots://tests/parser/protos/ProtoRestrictedFieldValues.proto" + +WorldInfo { +} +Viewpoint { + orientation 0.7470290283624516 0.646616024550071 0.15438700586161153 5.42889 + position -1.2077 1.6424 1.96945 +} +Background { + skyColor [ + 0.4 0.7 1 + ] +} +ParserTestSupervisor { +} +DirectionalLight { + ambientIntensity 1 + direction -0.33 -1 -0.5 + castShadows TRUE +} +Floor { + rotation 1 0 0 -1.5708 +} +ProtoRestrictedFieldValues { + rotation 0 1 0 1.5708 + translation 0 0 0 + extensionSlot [ + DerivedPoseProto { + } + Parameter { + } + Solid { + } + ] +} diff --git a/tests/parser/worlds/proto_not_allowed_mf_field_value_proto_no_subtypes.wbt b/tests/parser/worlds/proto_not_allowed_mf_field_value_proto_no_subtypes.wbt new file mode 100644 index 00000000000..e7929f94b24 --- /dev/null +++ b/tests/parser/worlds/proto_not_allowed_mf_field_value_proto_no_subtypes.wbt @@ -0,0 +1,42 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/manual_tests/protos/DerivedPoseProto.proto" +EXTERNPROTO "webots://tests/parser/protos/ParserTestSupervisor.proto" +EXTERNPROTO "webots://projects/objects/floors/protos/Floor.proto" +EXTERNPROTO "webots://tests/parser/protos/ProtoRestrictedFieldValues.proto" + +WorldInfo { +} +Viewpoint { + orientation 0.7470290283624516 0.646616024550071 0.15438700586161153 5.42889 + position -1.2077 1.6424 1.96945 +} +Background { + skyColor [ + 0.4 0.7 1 + ] +} +ParserTestSupervisor { +} +DirectionalLight { + ambientIntensity 1 + direction -0.33 -1 -0.5 + castShadows TRUE +} +Floor { + rotation 1 0 0 -1.5708 +} +ProtoRestrictedFieldValues { + rotation 0 1 0 1.5708 + translation 0 0 0 + extensionSlot [ + DerivedPoseProto { + } + Solid { + } + ], + extensionSlot2 [ + DerivedPoseProto { + } + ] +}