Skip to content

Unexpected behavior with hole and hull? #190

Description

@c-mauderer

Hello,

I'm not sure whether the behavior is expected or not. But for me it was a bit of a surprise: If I do a hull on two objects with holes, the result is a hull of the object minus a hull of the holes. To make it more clear: Here is an example:

#!/usr/bin/env python3

from solid import *
from solid.utils import *

c = cylinder(r=10) - hole()(cylinder(r=5))
x = hull()(c, left(10)(c))
print(scad_render(x))

This generates one long hole:

difference(){
        hull() {
                difference() {
                        cylinder(r = 10);
                }
                translate(v = [-10, 0, 0]) {
                        difference() {
                                cylinder(r = 10);
                        }
                }
        }
        /* Holes Below*/
        hull(){
                union(){
                        cylinder(r = 5);
                }
                translate(v = [-10, 0, 0]){
                        union(){
                                cylinder(r = 5);
                        }
                }
        } /* End Holes */ 
}

Screenshot

I would have expected two round holes:


difference(){
        hull() {
                difference() {
                        cylinder(r = 10);
                }
                translate(v = [-10, 0, 0]) {
                        difference() {
                                cylinder(r = 10);
                        }
                }
        }
        /* Holes Below*/
        union(){
                cylinder(r = 5);
        }
        translate(v = [-10, 0, 0]){
                union(){
                        cylinder(r = 5);
                }
        } /* End Holes */ 
}

Screenshot

Is that expected behavior?

Activity

  1. etjones commented on Nov 27, 2021

    @etjones
    Contributor

    I agree with your analysis; two circular holes is what we would expect intuitively. What's happening is that the hull() invocation is getting added to the negative holes as well as the positive areas, and that isn't what we expect.

    Let me have a think on this. We want this hull() to apply to the positive cylinders and NOT to the holes. But we could very reasonably want a hole that included a hull of negative space as well.

  2. c-mauderer commented on Nov 28, 2021

    @c-mauderer
    Author

    Let me have a think on this. We want this hull() to apply to the positive cylinders and NOT to the holes. But we could very reasonably want a hole that included a hull of negative space as well.

    That's correct. For example the following slightly modified code should generate one long "bar" with two slotted holes at the end. Instead it generates only one big hole with rounded edges.

    #!/usr/bin/env python3
    
    from solid import *
    from solid.utils import *
    
    slot = hull()([back(i)(cylinder(r=2)) for i in [-2, 2]])
    c = cylinder(r=10) - hole()(slot)
    x = hull()(c, left(50)(c))
    print(scad_render(x))
    
  3. jeff-dh commented on Nov 28, 2021

    @jeff-dh
    Collaborator
  4. etjones commented on Nov 28, 2021

    @etjones
    Contributor

    The hole() mechanism does basically what you’ve proposed, Jeff. In this particular situation, we’d need to disambiguate between: a hole() with hull() children (where the hull should persist in what’s subtracted) and a hull() with hole() grandchildren, where the hull should only apply to positive geometry.

    We could check for exactly that circumstance, but it feels hacky, like I don’t have a theoretical grasp of what’s going on there. That was some of the problem with the original implementation (there’s some “I think this works” comments in the source) but I’ve never fully understood the mechanics of how the hole setup should work in all situations. :-/

  5. c-mauderer commented on Nov 28, 2021

    @c-mauderer
    Author

    Thanks for the explanations and that you have a look at all these problems. I don't know the code well enough that I can really help a lot solving the problem. I just noted that I have some more edge cases where I'm really not sure about what to expect from holes (for example using an object with a hole as a hole to some other object - there are multiple reasonable interpretations what's right in this case). Is there some documentation about how the holes should work in different situations?

    PS: I solved my problem a bit different so that I don't need a workaround at the moment.

  6. etjones commented on Nov 28, 2021

    @etjones
    Contributor
  7. jeff-dh commented on Nov 28, 2021

    @jeff-dh
    Collaborator
  8. jeff-dh commented on Nov 28, 2021

    @jeff-dh
    Collaborator
    #! /usr/bin/env python
    
    from solid import *
    from solid.extensions.bosl2.std import cube, zcyl, diff
    
    def bosl2_diff_hull():
        obj = cube([100, 100, 10], _tags="pos")
        hole1 = zcyl(d=10, l=100, _tags="neg").translate(10, 10, 0)
        hole2 = zcyl(d=10, l=100, _tags="neg").translate(50, 50, 0)
    
        return diff("neg", "pos")(hull()(obj + hole1 + hole2))
    
    
    bosl2_diff_hull().save_as_scad()

    handles it the same (unexpected) way as the solidpython implementation

  9. jeff-dh commented on Nov 28, 2021

    @jeff-dh
    Collaborator

    hull(object) - hull(hole1) - hull(hole2)

    hmmmm this would create new "unexpected cases" if the holes are not convex shapes.....

    Your approach seems to be the way to go....

  10. jeff-dh commented on Nov 28, 2021

    @jeff-dh
    Collaborator

    Try to change line solid/solidpython.py:212 like this:

    	#from this:
    	if self.name in non_rendered_classes:
    		pass
    
    	#to this:
    	if self.name in non_rendered_classes or self.name == 'hull':
    		pass

    This seems to work at least for this examples:

    from solid import *
    
    c = cube([100, 100, 10])
    
    h1 = translate([20, 20, -20])(cylinder(r=10, h=100))
    h2 = translate([30, 30, -20])(cylinder(r=10, h=100))
    
    h3 = hull()(translate([50, 50, 0])(h1 + h2))
    
    hh = hull()(c - hole()(h1) - hole()(h2) - hole()(h3))
    
    scad_render_to_file(hh)
  11. jeff-dh commented on Nov 28, 2021

    @jeff-dh
    Collaborator

    @etjones I'm with you concerning other transformations. I was also wondering whether there might be more. But we could extend the line I proposed above to if not self.name in translationalNodesList: pass that might do the trick.

  12. etjones commented on Nov 28, 2021

    @etjones
    Contributor
  13. jeff-dh commented on Nov 28, 2021

    @jeff-dh
    Collaborator

    I think there may be a little more to it: does your change differentiate between hull(a, b - hole()(c)) (where the hull should be excluded from the hole) and a - hole()(hull(b,c)), (where the hull should be included)? I don’t think the current tree traversal pays attention to whether objects are above or below a given hole object in the hierarchy.

    If I understand it correctly, yeah it does.

    Take a look at the example snippet a posted above. This is the result of that code, which I think is what we would expect:
    hullholes

  14. etjones commented on Nov 28, 2021

    @etjones
    Contributor

    Excellent. Well, that’s a bit closer to a solution.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions