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

Add cq_cache decorator #17

Merged
merged 27 commits into from
Apr 21, 2021
Merged

Add cq_cache decorator #17

merged 27 commits into from
Apr 21, 2021

Conversation

Jojain
Copy link
Contributor

@Jojain Jojain commented Apr 1, 2021

Add a file based cache decorator function that allow to cache the result of a function creating a cadquery object.
When exploring design ideas from a complex geometry, rerunning the whole script at each change can be cumbersome. This plugin aims at helping on that.

It does it by writing a step file of the object created by decorated the function the first time a set of parameters is given and import this step if the script/the decorated function is run again with the same parameters.

While writing the tests I realized that the wrapper function doesn't necessarily return the same type of object as the decorated function. That's not goot.
I couldn't come up with a way to check the return object type of the decorated function so I'm asking help on this, if someone as any idea.

A solution could be to force the decorated function to return a specific type of object so the wrapper could return the same, but I think that makes the decorator less useful.

I'm happy to receive your feedback on this, thanks !

@adam-urbanczyk
Copy link
Member

adam-urbanczyk commented Apr 2, 2021

You probably want to use internal data format of OCCT to have no losses. So cq.Shape.exportBrep, I'll add cq.Shape.importBrep to enable this (also needed for CadQuery/cadquery#650 ).

@Jojain
Copy link
Contributor Author

Jojain commented Apr 2, 2021

Yes definitely, I searched for this on the cadquery doc but I'm not sure there info on it. I may just have missed it though.

@jmwright
Copy link
Member

jmwright commented Apr 2, 2021

@Jojain
Copy link
Contributor Author

Jojain commented Apr 2, 2021

Thanks I'll add this and I may have a solution for returning the right type of objects each time, will do that

changed from step to brep caching

Added logic to handle all the cadquery types and returning the good ones after importing cached geometry
@Jojain
Copy link
Contributor Author

Jojain commented Apr 8, 2021

This should be done now, I've switched to BREP files ( @adam-urbanczyk I wrote a importBrep function if you want to add it in cadquery) and I added some logic to be able to return the right type of object when decorating a function that returns a cadquery object.
I think I took care of everything but in the end it got a bit more complicated than expected so if you see any flaws let me know.

@jmwright
Copy link
Member

Thanks @Jojain this looks great. I think it would be helpful for there to be docstrings in all the cq_cache.py methods for us to reference in the future, and if I run black against the directory it still shows changes in cq_cache.py. I need to get the lint check fixed for PRs on this repo. I'll try to take a look at that tomorrow.

Copy link
Member

@marcus7070 marcus7070 left a comment

Choose a reason for hiding this comment

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

I haven't had a chance to look at this in depth, but this is what I see from a quick look. Very interesting plugin @Jojain!

plugins/cq_cache/cq_cache.py Outdated Show resolved Hide resolved
Comment on lines 67 to 75
def using_same_function(fct, file_name):
with open(file_name,"r") as f :
cached_function = "".join(f.readlines()[:-1])

caching_function = inspect.getsource(fct)
if cached_function == caching_function:
return True
else :
return False
Copy link
Member

Choose a reason for hiding this comment

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

This code on stackexchange might be of interest to you: https://codereview.stackexchange.com/questions/192848/python-function-to-hash-a-python-function

The author does a similar task but he ignores comments, docstrings and minor formatting changes. Also stores the result as a small hash instead of the complete code.

It's a fair bit of extra work though, it's up to you if you want to implement it.

Copy link
Contributor Author

@Jojain Jojain Apr 14, 2021

Choose a reason for hiding this comment

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

It doesn't seems too complicated so I might give it a try. Even though I'm not sure it will make more time gain overall than the one it's going to take me to implement this 😄

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, but I'll give you style points for implementing it. 🤣

Jojain and others added 3 commits April 14, 2021 21:46
@Jojain
Copy link
Contributor Author

Jojain commented Apr 18, 2021

@jmwright @marcus7070 I have added docstrings and tried to use hash instead of str for the identification of passed arguments.
I think the use of hashing for everything would probably better than the use of strings but honestly I don't really understand how it works and I don't want to spend time on trying to figure it out.
If someone more knowledgeable than me on this wants to implement more robust changes I see no problem.
If you are good with the changes we can call it done.

@jmwright
Copy link
Member

@Jojain Thanks for all the work you've put into this. The black check here on GitHub always fails for PRs because of a missing feature in GitHub. One thing I have on my list is to run black manually in CI and see if I can get that to work instead of the current Action from the Marketplace.

@jmwright jmwright self-requested a review April 18, 2021 18:36
@jmwright
Copy link
Member

@marcus7070 I'm ready to merge this. Let me know if you are too.

@marcus7070
Copy link
Member

@marcus7070 I'm ready to merge this. Let me know if you are too.

Not ready yet, I'm just exploring some of this hashing stuff. Python has deliberately broken the way I want to use the hash function, because repeatable hashes (when used in Python dicts) are a security vunerability. I'll try to push my changes later today.

* added a test to check if different arguments returned different cache
hits

* added a test for TypeErrors from Workplane arguments
Copy link
Member

@marcus7070 marcus7070 left a comment

Choose a reason for hiding this comment

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

I pushed some changes, have a look @Jojain and let me know if you have any questions or changes.

@jmwright after @Jojain has had a look (and yourself if you want) I'm happy for this to be merged.


# hash all relevant variables
hasher = hashlib.md5()
for val in [fct.__name__, args.__repr__(), kwargs.__repr__()]:
Copy link
Contributor Author

@Jojain Jojain Apr 20, 2021

Choose a reason for hiding this comment

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

How is this different from my initial way of doing it using str(arg) ?
From what I understand the difference with my initial code is that now instead of storing the str(arg) directly in the file name, you concatene everything is a resulting hash str. What for ?

Oh I wasn't on the right commit but this is still relevant.

Copy link
Member

Choose a reason for hiding this comment

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

How is this different from my initial way of doing it using str(arg) ?

Yeah, not much difference. I had thought I could get it much more accurate, but the hash implementation is mainly for supporting dicts and isn't suitable.

It's possible to hash the bytes output from pickle. This would be as accurate as I was hoping for, but you can't pickle most CQ types, so it isn't practical for us.

repr is slightly better than str, since:

>>> str(1) == str("1")
True
>>> repr(1) == repr("1")
False

I haven't solved the main problems with a str(arg) based caching though, mainly that any complicated object (including lambdas, generators, cq.Plane and cq.Location) will never compare the same and will be a seperate cache value each time the function is called. 🤷

From what I understand the difference with my initial code is that now instead of storing the str(arg) directly in the file name, you concatene everything is a resulting hash str. What for ?

The hash string is a fixed length, this could easily blow out past filename limits with large args. Especially given it's going to be used on computationally heavy functions where you wouldn't be surprised to see a large array of (x, y, z) tuples as an argument.

Copy link
Contributor Author

@Jojain Jojain Apr 20, 2021

Choose a reason for hiding this comment

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

Alright thanks for the explanation. But I'm wondering why we can't we pickle CQ object ?

Copy link
Member

@marcus7070 marcus7070 Apr 20, 2021

Choose a reason for hiding this comment

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

I don't have much experience with pickle. I gave it a try and it errored on simple CQ objects. We might be able to get it working by adding __getstate__ methods on a few CQ classes.

@Jojain
Copy link
Contributor Author

Jojain commented Apr 20, 2021

I just had some questions but I'm good with the changes ! Thanks @marcus7070 you found a nice way to remove the use of eval. that's great !

@Jojain
Copy link
Contributor Author

Jojain commented Apr 20, 2021

We might also want to wait CadQuery/cadquery#735 to be resolved and use the importBrep implementation from cadquery ? (Altough it should be identical so we can merge it as it is)

@marcus7070
Copy link
Member

We might also want to wait CadQuery/cadquery#735 to be resolved and use the importBrep implementation from cadquery ? (Altough it should be identical so we can merge it as it is)

I would say merge now and fix that later.

@jmwright
Copy link
Member

I see that @Jojain gave a thumbs-up on merging, so I'm going to go ahead and do that now. Thanks for the contribution @Jojain !

@jmwright jmwright merged commit e62bf3d into CadQuery:main Apr 21, 2021
@Jojain Jojain deleted the cq_cache branch April 22, 2021 20:24
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.

4 participants