Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed classes to inherete from compas.Data #387

Merged
merged 13 commits into from
Nov 8, 2023
Merged

Conversation

yck011522
Copy link
Contributor

@yck011522 yck011522 commented Oct 4, 2023

Some of the classes in compas_fab is still based on (object) instead of (Data)

I have selected some of the classes that should support serialization and upgrade them. Upgrading them should be backward compatible. However, it may affect user classes inherited from these classes. This RP does not cover all the classes yet (e.g. classes in backends, reachability map, ghpython and some more). Let me know if you think I should extend to cover all classes.

@gonzalocasas Should I add test files for this?

What type of change is this?

  • Bug fix in a backwards-compatible manner.
  • New feature in a backwards-compatible manner.
  • Breaking change: bug fix or new feature that involve incompatible API changes.
  • Other (e.g. doc update, configuration, etc)

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • I added a line to the CHANGELOG.md file in the Unreleased section under the most fitting heading (e.g. Added, Changed, Removed).
  • I ran all tests on my computer and it's all green (i.e. invoke test).
  • I ran lint on my computer and there are no errors (i.e. invoke lint).
  • I added new functions/classes and made them available on a second-level import, e.g. compas_fab.robots.CollisionMesh.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if appropriate)

Copy link
Member

@gonzalocasas gonzalocasas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classes that inherit from Data should also add implementations for the data property getter and setter, and I think some of these ones dont. At least RobotSemantics. Also, the Data class implements a default from_data class method that works only if the class has a constructor with all optional params, if that's not the case, there needs to be an overload of from_data in the class that is able to create instances (usually by first getting the args needed from the data dictionary, constructing with the minimum, and then calling the .data = setter.

@gonzalocasas
Copy link
Member

Should I add test files for this?

yes please! they don't need to be super elaborate, but testing the serialization and deserialization on all these classes with some basic data would help ensure they behave properly all the time

@yck011522
Copy link
Contributor Author

I have revised the list of affected classes:

  • CollisionMesh (using existing to_data() from_data() )
  • AttachedCollisionMesh (using existing to_data() from_data() )
  • Robot (using existing data getter setter)
  • RobotSemantics (added new data getter setter and to_data())
  • Tool (using existing data getter setter)

The following class is skipped as it maybe redesigned in near future

  • PlanningScene

@yck011522 yck011522 marked this pull request as ready for review October 5, 2023 08:59
@yck011522
Copy link
Contributor Author

ping @gonzalocasas

Copy link
Member

@chenkasirer chenkasirer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. left some comments though.

Comment on lines 82 to 84
self._scale_factor = data.get("_scale_factor", 1.0)
self._attached_tools = data.get("_attached_tools", {})
self._current_ik = data.get("_current_ik", {"request_id": None, "solutions": None})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmh is this to stay compatible with previously serialized Robots? But it didn't implement the Data interface before, so there shouldn't be any, right?

You could also simply omit these from the Data dictionary if they take default values, __init__ is ran first anyways. Otherwise you can assume they're there, because they're put there by the getter.

Comment on lines 68 to 70
"_scale_factor": self._scale_factor,
"_attached_tools": self._attached_tools,
"_current_ik": self._current_ik,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the _s?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't invent this particular variable, and it was there all along, I just serialized it. I believe the intent of the existing code is that the model is not supposed to keep track of extrinsic or modifiable properties of the robot (in this case, the current_ik ). Hence the private _ notation. I should probably have another PR to address the intrinsic vs extrinsic state of the robot problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the _ in the keys.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, current_ik should not be serialized

Comment on lines +71 to +75
@classmethod
def from_data(cls, data):
robot_semantics = cls(None)
robot_semantics.data = data
return robot_semantics
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gonzalocasas commented on this. I'd say setting robot_model=None in arguments of __init__ is legit. Then you don't need a custom from_data. While I was not a fan, this convention grew on me.

Having said that, the trend in COMPAS 2.0 is to get rid of the data.setter acknowledging it's often redundant. So you'd then just have a data getter and a from_data which feeds the data dict to __init__.

Example (just for illustration, not sure if this works in in LTS): gramaziokohler/compas_timber@d11d83e#diff-5cbdc4545536c895404b80de4fdcc522ec5b95dfb4af73a47e4120888cc22031R42-R48

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I get your argument.

Are you saying that the style is moving towards having default values for all parameters in the __init__ and then skipping the def from_data()?

Probably it would be good to have a more wholistic stylistic clean up separately. The style currently used in compas_fab is a mixed bag of things. I can do that as long as we can have some consensus on what convention we adopt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea sorry didn't wanna cause confusion. Let's leave it like that.

tests/robots/test_robot.py Outdated Show resolved Hide resolved
assert tool1.name == tool2.name
assert str(tool1.frame) == str(tool2.frame)


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add another unittest checking deepcopy. should behave the same as (de)serializing but sometimes doesn't

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried deepcopy but It doesn't seem to work. I think some years ago, I heard from someone to not use deepcopy on these objects. I don't remember why. But basically I never deepcopied any compas objects since.

Just to give you a taste of that error message:

./tests/robots/test_robot.py::test_robot_deepcopy_with_tool Failed: [undefined]AttributeError: 'Visual' object has no attribute '_guid'
tests\robots\test_robot.py:345: in test_robot_deepcopy_with_tool
    robot2 = deepcopy(robot)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:271: in _reconstruct
    state = deepcopy(state, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:271: in _reconstruct
    state = deepcopy(state, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:271: in _reconstruct
    state = deepcopy(state, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:206: in _deepcopy_list
    append(deepcopy(a, memo))
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:271: in _reconstruct
    state = deepcopy(state, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:231: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:146: in deepcopy
    y = copier(x, memo)
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:206: in _deepcopy_list
    append(deepcopy(a, memo))
..\..\..\anaconda3\envs\cf-dev\Lib\copy.py:161: in deepcopy
    rv = reductor(4)
..\..\..\anaconda3\envs\cf-dev\Lib\site-packages\compas\data\data.py:121: in __getstate__
    "guid": str(self.guid),
..\..\..\anaconda3\envs\cf-dev\Lib\site-packages\compas\data\data.py:200: in guid
    if not self._guid:
E   AttributeError: 'Visual' object has no attribute '_guid'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's known deepcopy errors produce the most beautiful stacks..
To this specific one, could there be a class called Visual (really?) somewhere which inherits from Data but doesn't call super().__init__()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone wants to debug this, this is the test code:

def test_robot_deepcopy(panda_robot_instance):
    import copy
    robot = panda_robot_instance
    robot.scale(0.001)
    robot2 = copy.deepcopy(robot)
    assert robot is not robot2

def test_robot_deepcopy2(ur5_robot_instance, robot_tool1):
    import copy
    robot = ur5_robot_instance
    robot.attach_tool(robot_tool1)
    robot2 = copy.deepcopy(robot)
    assert robot is not robot2

Both test won't work and they have different error message

@yck011522
Copy link
Contributor Author

ping @gonzalocasas

Copy link
Member

@gonzalocasas gonzalocasas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM, I added some feedback though

Comment on lines 68 to 70
"_scale_factor": self._scale_factor,
"_attached_tools": self._attached_tools,
"_current_ik": self._current_ik,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the _ in the keys.

Comment on lines 68 to 70
"_scale_factor": self._scale_factor,
"_attached_tools": self._attached_tools,
"_current_ik": self._current_ik,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, current_ik should not be serialized

src/compas_fab/robots/semantics.py Show resolved Hide resolved
tests/robots/test_robot.py Outdated Show resolved Hide resolved
src/compas_fab/robots/robot.py Outdated Show resolved Hide resolved
@gonzalocasas
Copy link
Member

Cool, thanks for the updates! On my side, I think this is ready to go!

@yck011522 yck011522 merged commit 88f3551 into main Nov 8, 2023
8 checks passed
@yck011522 yck011522 deleted the migration_data_class branch November 8, 2023 04:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants