-
Notifications
You must be signed in to change notification settings - Fork 81
Add option to retain face data #74
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
Conversation
README.md
Outdated
@@ -36,6 +36,7 @@ A more complex example | |||
* `strict` (Default: `False`) will raise an exception if unsupported features are found in the obj or mtl file | |||
* `encoding` (Default: `utf-8`) of the obj and mtl file(s) | |||
* `create_materials` (Default: `False`) will create materials if mtl file is missing or obj file references non-existing materials | |||
* `consume_faces` (Default: `False`) will consume a face list for every material as well. Beware that it will greatly increase memory usage for large models. |
There was a problem hiding this comment.
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 "consume" is an understandable word to use here. If I didn't know the details of the parser I would be confused about this option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I liked "collect" much better. "consuming" is just a parser term of processing data and not necessarily related to what we do with the data itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, renamed it back.
pywavefront/obj.py
Outdated
@@ -336,6 +352,9 @@ def consume_faces(self): | |||
# Do we need to triangulate? Each line may contain a varying amount of elements | |||
triangulate = (len(self.values) - 1) > 3 | |||
|
|||
if consumed_faces is not None: | |||
consumed_faces.append([None] * (len(self.values) - 1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I would collect the face list as triangles only. Then we know each face is 3 elements in the face list. Right now for some models it would be impossible to understand the face list as some f
statements can contain a variable amount of indices for each f
statement (3 or more). Ensure you append
inside the triangulate check as well.
I would also keep it simple and just append()
to consumed_faces
instead of pre-allocate space like this. Python lists over-allocate internally so memory allocations will be less and less frequent as the list increases in size. It makes more sense if you pre-allocate a lot of indices with list.extend
.
I wish there was a way to use generators to collect this data (faster!), but since we also support python 2 we don't have yield from
to do the magic.
There was a problem hiding this comment.
Choose a reason for hiding 8000 this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now for some models it would be impossible to understand the face list as some f statements can contain a variable amount of indices for each f statement (3 or more).
Yes, this was actually intended. I am not sure how much preprocessing this library is supposed to do. Maybe someone (like me) just wants to access the raw face data from a Wavefront file. Especially, I could not find any other Python Wavefront parsing library (!).
In #73 it sounded a lot that this library is mainly targeted at "preparing interleaved vertex data". I described my use case in this comment at #73. I might be lucky that my Wavefront file only has triangle faces anyway, but if it had quad faces, I would have needed to roll my own library and thus duplicating a lot of raw Wavefront parsing. This seems unnecessary to me this project could distinguish between "raw parsing" and "preprocessing".
I would also keep it simple and just append() to consumed_faces instead of pre-allocate space like this.
Note that I don't preallocate the overall face list, but I preallocate the list of vertex indices of each and every face, i.e. I preallocate lists of 3 elements most of the time for my Wavefront files. So no amortization effects apply. But sure, one might call this "premature optimization". I'd argue in favor of keeping it because it is a very trivial line and also not decreasing readability/maintainbility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you need any help, don't be afraid to ask.
pywavefront/obj.py
Outdated
@@ -336,6 +352,9 @@ def consume_faces(self): | |||
# Do we need to triangulate? Each line may contain a varying amount of elements | |||
triangulate = (len(self.values) - 1) > 3 | |||
|
|||
if collected_faces is not None: | |||
collected_faces.append([None] * (len(self.values) - 1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Paste from outdated review:
Personally I would collect the face list as triangles only. Then we know each face is 3 elements in the face list. Right now for some models it would be impossible to understand the face list as some f statements can contain a variable amount of indices for each f
statement (3 or more). Ensure you append inside the triangulate check as well.
I would also keep it simple and just append()
to collected_faces
instead of pre-allocate space like this. Python lists over-allocate internally so memory allocations will be less and less frequent as the list increases in size. It makes more sense if you pre-allocate a lot of indices w
8000
ith list.extend.
Since we also support python 2 here we don't have access to yield from
magic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 2 3 # 1 triangle, 3 indices
3 4 5 6 # 2 triangles, 6 indices
7 8 9 10 11 # 3 triangles, 9 indices
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the example! Did you miss my previous comment by any chance (link)? What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes I missed it.
I think we should at least triangulate when collecting faces so new features can possibly be built on top of that.
test/test_parser.py
Outdated
@@ -12,7 +12,7 @@ def prepend_dir(file): | |||
class TestParsers(unittest.TestCase): | |||
def setUp(self): | |||
# Append current path to locate files | |||
meshes = pywavefront.Wavefront(prepend_dir('simple.obj')) | |||
meshes = pywavefront.Wavefront(prepend_dir('simple.obj'), collect_faces=True, cache=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make a separate test class for collect_faces
. This way we test the parser with and without the new feature.
class TestParserCollectFaces(TestParsers):
# Override setup
def setUp(self):
meshes = pywavefront.Wavefront(prepend_dir('simple.obj'), collect_faces=True)
self.mesh1 = meshes.mesh_list[0]
self.mesh2 = meshes.mesh_list[1]
# ... add your own test methods here ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason you are getting errors is that other tests also extend TestParsers
overriding the setUp
methods, so they sabotage they tests you added in TestParsers
. That is why you need to make your own class for testing the feature 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright! Will do.
The CI tests are failing because I used
|
The rest looks reasonable. |
Are there any metrics on python2 vs python3 support in other projects? What is the "best practice"? Trivia: while investigating this question, I found https://pythonclock.org |
LGTM as well. Ready to merge, pending either a passing test, or a compelling reason to drop Python 2 support. |
I would suggest: Release a new major version: 1.0.0 that is python 3 only, update README and tests. We can simplify a lot of things in the current code base with py3. Having |
@greenmoss Are there any statistics on PyPI to see how many download of this package originated from a Python 2 installation?
Is there any reason why the README states only 3.6+ support instead of, say, 3.5+? I picked the lowest 3.x version number, i.e. 3.4, for CircleCI now. (My actual use case for this library sadly still depends on 3.5.) |
I guess the python version info in readme is a bit confusing. Feel free to tweak. Dropping py2 in a separate PR make sense I guess. |
@greenmoss Is there any consensus on how to continue from here? @einarf I am not sure if rewriting this PR to Python 2 is worthwhile if the project will switch to Python 3 anyway. |
This is up to @greenmoss really. Option 1:
Then we could move ahead and strip away the python 2 stuff. It doesn't create any issues as it is now. py2 is EOL soon and most people have moved to python 3 a long time ago. Option 2:
Honestly when I think of it, with the activity level of this project and the fact that it's fairly small I would personally pick option 1. It's a driver to make more things happen in the project. Being stuck in the python 2/3 shared world is not fun. Doing the recent parser revamp was not a fun experience for me. Just make sure to build releases with python3 for the 1.0.0 releases and don't make them universal. That means pip under python2 will pick 4.x releases. |
Option 1 is fine for me. I'll see if I can get a release out this evening. |
Release |
@greenmoss There should be no py2 stuff standing in the way. The main thing is to update circleci to use py3 for tests and re-run tests for this PR + resolve the conflict in setup and merge. |
@einarf interesting I see |
#76 should fix the python 3.4 issue. Rebase (or merge in a fresh master) this PR after merge and we're good for python 3.4 👍 |
Finally the tests are passing! @greenmoss This PR is ready to merge 😄 |
@ComFreek there are two un-checked check boxes in the comments above:
Are these still pending, or do you want me to merge as is? |
@greenmoss I ticked them both now. The first comment has been addressed in my latest commit in this PR. The second comment is obsolete now that @einarf fixed that issue in the other PR and we dropped Python 2 support in the master branch anyway. Thanks for merging! |
Yeah, saw that and merged. Thanks! |
(Follow-up from #73.)
Naming comments
I decided to choose
consume_faces
overcollect_faces
to remain consistent in the naming scheme already used forconsume_vertices
and other methods.Tests fail!
Strangely enough, running the tests with
nosetests
will fail intest_parsers.py
/classTestParsers
. It seems thatconsume_faces
doesn't get propagated to the Material objects.However, running the class
TestParsers
in isolation withnoosetests .\test\test_parser.py:TestParsers
works.I also tried
cache=False
, but that didn't change anything.