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

Adding Sphere object #75

Merged
merged 14 commits into from
Nov 20, 2019
Merged

Adding Sphere object #75

merged 14 commits into from
Nov 20, 2019

Conversation

santiagogaray
Copy link
Contributor

Initial class with basic functionality.

@chriswmackey
I created a bare bones Sphere class to get the PR started. I see in the project discussion that initially we wanted to add some functionality in line to the other ladybug-geometry classes. Additionally I saw you pointed out to this link for reference for math calculations:
https://github.com/ezag/pyeuclid/blob/master/euclid.py#L2033-L2114
This seems to focus on intersection type of algorithms. I wonder if it's this what we are after in terms of added functionality. Do we need to discuss what functionality is needed from this object?

Initial class with basic functionality.
@chriswmackey
Copy link
Member

Hey @santiagogaray ,

Thanks for putting this together and I think there's a lot more we'd want to add like the following properties

  • min
  • max
  • diameter
  • circumference
  • area
  • volume

... and methods:

  • intersect_line_ray
  • intersect_plane

Note that the only intrinsic attributes should be _center and _radius as you have them and all of the other properties are just converted to/from these intrinsic ones.

I'll do a longer code review after you have gotten the chance to implement some of these and write some tests for them.

@santiagogaray
Copy link
Contributor Author

Thanks @chriswmackey for clarifying the initial functionality. Will start working on it!

@santiagogaray
Copy link
Contributor Author

Hi @chriswmackey

I updated the Sphere with the properties listed and a first take at the intersection methods.

I have a few questions when you get a chance:

  • On the properties I only added the getters for now, are we interested in adding any setters?

  • I used a method that finds intersections between lines and spheres, where a line's pt1 and pt2 are needed. I wonder if this can be adapted to a ray, have you've done something like that before?

  • Are we even using a line at all or will always be translated to a ray (extended to infinite line).

  • How have you tested intersections in the past? I find it hard to test their accuracy by just seeing the results. Do you actually display them in some way?

No rush Chris, whenever you can!

@chriswmackey
Copy link
Member

Hey Santiago,

Sorry for the late response. I'll do a full review shortly but I'll first answer your questions and highlight that your tests are failing in Python 3 because you have a print statement without parentheses: https://travis-ci.org/ladybug-tools/ladybug-geometry/jobs/608959731?utm_medium=notification&utm_source=github_status
Generally speaking, we shouldn't be using print statements in the core libraries and, if you need to give an info message to a user, it is better to log it.

Now to answer your questions:

  1. You will see that all of the objects in ladybug_geometry are immutable (they are not editable once they are created). This immutability helps a lot in Grasshopper workflows where each Grasshopper component needs to duplicate a given geometry object before editing it (otherwise, the output of the previous component will be mutated). Many of the honeybee components are only editing energy simulation properties, which don't have anything to do with geometry itself and are instead about insulation level, occupancy profiles, etc. So it saves a lot of memory to not duplicate the geometry itself with each component, which we can do safely if we know that they geometry object is not editable. So all of this means that there shouldn't be any setters on ladybug_geometry objects.

  2. The inputs to the intersection calculations should be other ladybug_geometry objects. So it should be either a LineSegment3D or a Ray3D (not 2 points). If you look deep into the ladybug_geometry code, you will see that LineSegment3Ds and Ray3Ds are essentially represented the same way and so I think you should be able to adapt one intersection function to the other. And, if you look deeper into the euclid.py code, you will probably find that the intersection calculations for an infinite line, a ray, and a line segment are all essentially the same. It's just that the latter two also do a check to see if the intersection point lies in the domain of the ray or segment.

  3. I would suggest putting the raw math for the intersection calculation into the intersection3d module. Just having one method that takes a LineSegment3D or a Ray3D along with a Sphere3D would be fine. Then you can add a intersect_line_ray method to the Sphere3D object that call this function.

  4. For the Python unit tests, I typically only test a few straightforward cases that are easy to visualize without the help of CAD. However, I often do some testing afterwards in Rhino by writing a simple translator between the ladybug_geometry and Rhino object as you see here. If you implement the Sphere3D, it would be great if you could also send a PR to ladybug_rhino with the appropriate translators.

Copy link
Member

@chriswmackey chriswmackey left a comment

Choose a reason for hiding this comment

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

Thanks for getting this together @santiagogaray . I think if you can address my comments and write a few more tests, we will be able to merge this in.

ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
@santiagogaray
Copy link
Contributor Author

santiagogaray commented Nov 11, 2019

Thanks for the detailed review @chriswmackey.
I only updated for now the Sphere.intersect_plane math to fix some bugs found after some further testing.
I will work on fixing the intersect_line_ray math and all the changes listed above.

- Fixing some code style issues
- Fixing several documentation issues
- Separating the math of the intersection functions out of the Sphere3D class as an intermeiate step before moving them to the intersection3D class.
@santiagogaray
Copy link
Contributor Author

santiagogaray commented Nov 12, 2019

Thanks again @chriswmackey. I've learned a few things from your review. I appreciate the time to explain things that clearly. I corrected almost everything . I included also the Point3D to_array and from_array functions for your review.

The only change I am having problems with is moving the intersection functions to intersection3D module. Once I move them I am running into some problems with the tests. Seems like pytest cannot find certain modules once I import the intersection methods in the Sphere3D class . It is very strange and cannot find any mistake on my side. Definitely something that I'm missing that may be new to me.

For your review, I thought would be better to add first a commit with the functions inside the Sphere3D module that is working well and follow it with a new commit with the functions moved to the intersection3D module and hopefully show any mistakes I made through the changes involved.

@santiagogaray
Copy link
Contributor Author

santiagogaray commented Nov 12, 2019

This last commit has the math moved to intersection3d. For reference this is the error message:
____________________ ERROR collecting tests/sphere_test.py ____________________
ImportError while importing test module 'C:\Users\sgara\Documents\GitHub\ladybug-geometry\tests\sphere_test.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests\sphere_test.py:5: in
from ladybug_geometry.geometry3d.sphere import Sphere3D
ladybug_geometry\geometry3d\sphere.py:6: in
from ..intersection3d import intersect_line3d_sphere3d, intersect_plane_sphere3d
ladybug_geometry\intersection3d.py:6: in
from .geometry3d.plane import Plane
ladybug_geometry\geometry3d\plane.py:6: in
from .ray import Ray3D
ladybug_geometry\geometry3d\ray.py:6: in
from ._1d import Base1DIn3D
ladybug_geometry\geometry3d_1d.py:6: in
from ..intersection3d import closest_point3d_on_line3d,
E ImportError: cannot import name closest_point3d_on_line3d

@chriswmackey
Copy link
Member

Thanks for addressing all of the comments, @santiagogaray . I will look over everything in detail shortly but I just wanted to say that the reason why the test is failing is because importing Arc3D within the intersection3d module creates a circular import because the Arc3D class itself uses other functions within the intersection module. So Python gets confused about which module should be imported first and just throws an exception.

You will see that I have been solving this issue to-date by expressing the output of the intersections purely as Point3Ds, Vector3Ds, and/or numbers. For example, the intersect_plane_plane method returns a Point3D and a Vector3D for the infinite line representing the intersection between the two planes. For your case here, I think you can represent the Arc of the Sphere/Plane intersection with 3 criteria:

  1. A Point3D for the Arc center
  2. A Vector3D for the normal of the plane in which the arc sits
  3. A number for the radius of the circle

Then, on the Sphere3D class, you can have the intersect_plane method call this "pure math" intersection function and use its output to construct an Arc3D that you return from the method.

I realize that this organization of the code seems a bit roundabout but, so far, I have found it helpful to have all of the intersection math stored in one place and, as you can see by all of the modules that import from the interstion3d module, many of these intersection math functions end up being shared by several of the geometry objects.

I am also cool if you just want to implement the intersection math on the Sphere3D class right now and move them to the intersection3d module later.

Copy link
Member

@chriswmackey chriswmackey left a comment

Choose a reason for hiding this comment

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

Hey Santiago. This is looking good and thanks for making the changes. Most of my comments are on typos in the docstrings. If you could take care of those and implement one of the solutions I mentioned for the intersection functions, we should be good to merge. Also, as you'll see in the comments, it would also be good if you can add rotate and reflect functions and I think these should be pretty straightforward.

ladybug_geometry/intersection3d.py Outdated Show resolved Hide resolved
ladybug_geometry/intersection3d.py Outdated Show resolved Hide resolved
ladybug_geometry/intersection3d.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
ladybug_geometry/geometry3d/sphere.py Outdated Show resolved Hide resolved
If None, it will be scaled from the World origin (0, 0, 0).
"""
return Sphere3D(self.center.scale(factor, origin), self.radius * factor)

Copy link
Member

Choose a reason for hiding this comment

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

It would be good if you could include methods for rotate and reflect like the other geometry objects. I know that those two don't get used as much as the move and scale methods do but they have their usefulness and I am trying to ensure that all geometry objects support all 4 of the similar transformations:

  • move
  • scale
  • rotate
  • reflect

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad! With the sphere not having an orientation and wrongly assuming these where local transformations. Just needed to see the inputs, no excuse!

I added the rotate_xy since I see that is implemented in other classes as well. Hope that's okay.

@santiagogaray
Copy link
Contributor Author

Thanks @chriswmackey. I have to say I am ashamed on a few typos and oversights. I have to learn to to be more careful, and not rush on tasks that may look straightforward.

I did all the changes listed (Hopefully!).
Thanks for clarifying the circular import issue. A new learning moment, so I appreciate the explanation and suggested solutions. I think I did with the best option: I sent the data (Point3D, Vector3D, numbers)from the intersection3D functions and generated the different geometry in the Sphere3D methods.
Also made sure to account for the tangent lines to Sphere returning a Point3D.
I also returned a Point3D from a tangent plane intersecting a sphere as this could be useful in the future. In this case though I discarded the Point3D in the Sphere3D method, returning None.

I hope I didn't miss anything. Once this PR is closed I will be ready to start the next object.

@chriswmackey chriswmackey self-requested a review November 15, 2019 20:16
Copy link
Member

@chriswmackey chriswmackey left a comment

Choose a reason for hiding this comment

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

Thank you for addressing all of the comments, @santiagogaray , and there's no need to be ashamed of typos. They are a natural part of developing code and one of the major reasons why we do code review. It's also another reason why we try to write tests for all of our code (usually we can surface a typo or two in the process of writing them). I am approving your PR since your code looks good enough to merge but one thing that would be really helpful before we merge is if you could write some tests for all of the methods on the Sphere3D. You can see here that coveralls shows you the lines of your code that have tests for them and which ones lack tests. Personally, I try to get my code above 90% before I merge it in, though it's more important to write tests that meaningfully check your workflows than it is to get everything 100% covered.

As for your next PR, tackling the other objects (eg. cone, cylinder) would be great but I also wanted to note now that you have added the to_array and from_array methods in this PR, another really helpful task would be going through all the to_dict and from_dict methods on all of the geometry objects (except points and vectors) and replacing all of the code like this with your new array methods. The call is up to you but I know that task might be a little quicker.

@santiagogaray
Copy link
Contributor Author

Thanks @chriswmackey. So much to learn, finally I understand the coverage test, extremely useful! I did bump the coverage to 98% and I actually found a bug in the copy function, so this proving to be a good strategy already.

It definitely make sense to work on the to_array and from_array functions of existing geometry objects, so will start that PR next week before getting into a new 3D object. Talk to you then!

@chriswmackey
Copy link
Member

Thanks for adding all of the tests, @santiagogaray and I am glad to hear the coverage is that high. I just have one small tweak that I would recommend in the way that you structured your tests, which will make it easy to identify what is failing if we ever make changes in the future. Specifically, the fact that you are testing so many things in a single function as you are here isn't the best practice since, if one of them fails, it's hard to tell where exactly the problem was (ie. does a failure in the moving of the object really mean a failure in this object init as your test name suggests). Breaking tests down to test individual features like you see here helps clarify this.

Also one other note is that, if you want to test the string representation of an object, it's better practice to just write str(sphere) instead of print(sphere) as you did here. Depending on how you run the tests, the printed info may end up in your debugger and, if this happens many times across many tests, it can interfere with you actually seeing the failing tests and error messages.

- Renaming test file to sphere3d_test
- Separating Sphere3D tests by functions
- Adding additional tests
@santiagogaray
Copy link
Contributor Author

Thanks @chriswmackey for this explanation. I am clearly very new to all these testing practices. I thought this was merely internal for me to check the code and maybe as a log of what has been done. Now I'm just realizing this is a test that will be run in future development of the module to make sure things stay bug-free. Is really great to finally understand this, loving the Github environment the more I understand it.
So knowing this I did some cleanup, separated the tests by function and followed closer the style of your tests. I hope this looks good enough but happy to review it as many times as needed.

@chriswmackey
Copy link
Member

Wonderful! Thank you for the quick edits, @santiagogaray . You have some very clean, well-documented code and tests here that should be able to stand the test of time! I'll merge this in now.

@chriswmackey
Copy link
Member

chriswmackey commented Nov 19, 2019

@santiagogaray , it looks like you closed the pull request. Was this intentional or accidental (I know we were considering including some of the changes mentioned here in your next PR)?

@chriswmackey chriswmackey reopened this Nov 19, 2019
@chriswmackey
Copy link
Member

I just reopened it and I'll interpret the closing as accidental and merge it in unless you tell me not to within the next 5 minutes.

@santiagogaray
Copy link
Contributor Author

Sorry @chriswmackey, a newbie move. Please go ahead and merge it in.

@chriswmackey chriswmackey merged commit 7792f85 into ladybug-tools:master Nov 20, 2019
@ladybugbot
Copy link

🎉 This PR is included in version 1.8.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants