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

Difference between linearly extruded polygon and polyhedron generates incorrect surface #4325

Closed
Teraslilja opened this issue Aug 11, 2022 · 6 comments

Comments

@Teraslilja
Copy link

Teraslilja commented Aug 11, 2022

The attached piece of code demonstrates beams with cut heads, but unfortunately there are two issues visible

  1. white beams with cut heads have incorrect/missing/invisible surfaces (image attachment n:o 1)
  2. even though the surfaces should be cut away, there still exists Z-fight between cut parts of heads (image attachment n;o 2)
beam_height = 150;
beam_width = 103;
beam_wall_thickness = 10;
beam_coupling = 12;
beam_gap = 12-beam_coupling;

module shaped_beam(length, profile) {
  color("SaddleBrown")
    rotate(90,[0,1,0])
      rotate(90,[0,0,1])
        linear_extrude(height = length,convexity=10)
          polygon(points = profile);
}

module beam(length, extra=0) {
  profile = [
    [-beam_width/2,-beam_height/2],
    [-beam_width/2,beam_height/2],
    [beam_wall_thickness-beam_width/2,beam_height/2],
    [beam_wall_thickness-beam_width/2,beam_height/2+beam_coupling],
    [beam_width/2-beam_wall_thickness,beam_height/2+beam_coupling],
    [beam_width/2-beam_wall_thickness,beam_height/2],
    [beam_width/2,beam_height/2],
    [beam_width/2,-beam_height/2],
    [beam_width/2-beam_wall_thickness,-beam_height/2],
    [beam_width/2-beam_wall_thickness,beam_coupling-beam_height/2],
    [beam_wall_thickness-beam_width/2,beam_coupling-beam_height/2],
    [beam_wall_thickness-beam_width/2,-beam_height/2],
    [-beam_width/2,-beam_height/2],
  ];
  shaped_beam(length, profile);   
}

module beam_head_cut_impl3(cut) {
  extra = 25;
  middle_head_z = [0,0,((beam_gap-beam_coupling)/2+beam_coupling)/2-0.5];
  faces = [
    [0,1,2,3],
    [2,3,7,6],
    [4,5,1,0],
    [4,7,3,0],
    [5,6,2,1],
    [4,5,6,7],
    ];
  points_hi = [
    [-extra,-extra,beam_height+beam_coupling+extra],
    [-extra,+beam_width+extra,beam_height+beam_coupling+extra],
    [+beam_width,+beam_width+extra,beam_height+beam_coupling+extra],
    [+beam_width,-extra,beam_height+beam_coupling+extra],
  ];
  points_low = [
    [-extra,-extra,-extra],
    [-extra,+beam_width+extra,-extra],
    [+beam_width,+beam_width+extra,-extra],
    [+beam_width,-extra,-extra],
  ];
  points_cut_face = [
    [0,0,(beam_height+beam_coupling)/2],
    [0,+beam_width,(beam_height+beam_coupling)/2],
    [+beam_width,+beam_width,(beam_height+beam_coupling)/2],
    [+beam_width,0,(beam_height+beam_coupling)/2],
  ];
  if(cut=="IN") {
    delta = [
      middle_head_z+[0,0,+45],
      middle_head_z+[0,0,+35],
      middle_head_z+[0,0,+25],
      middle_head_z+[0,0,+35],
    ];
    color("SaddleBrown")
      translate([0,-beam_width/2,-beam_height/2])
        union() {
          polyhedron(concat(points_hi,points_cut_face+delta),faces,convexity=10);
          polyhedron(concat(points_low,points_cut_face-delta),faces,convexity=10);
        }
    echo(" IN: high: ",points_cut_face+delta);
    echo(" IN:  low: ",points_cut_face-delta);
  } else if(cut=="OUT")
  {
    delta = [
      middle_head_z+[0,0,+35],
      middle_head_z+[0,0,+45],
      middle_head_z+[0,0,+35],
      middle_head_z+[0,0,+25],
    ];
    color("SaddleBrown")
      translate([0,-beam_width/2,-beam_height/2])
        union() {
          polyhedron(concat(points_hi,points_cut_face+delta),faces,convexity=10);
          polyhedron(concat(points_low,points_cut_face-delta),faces,convexity=10);
        }
    echo("OUT: high: ",points_cut_face+delta);
    echo("OUT:  low: ",points_cut_face-delta);
  }
}

module cut_beam(head1,length,head2) {
  difference() {
    beam(length);
    if(head1!="UNCUT")
      translate([0,0,0])
        beam_head_cut_impl3(head1);
    if(head2!="UNCUT")
      translate([length,0,0])
        mirror([1,0,0])
          beam_head_cut_impl3(head2);
  }
  *if(head1!="UNCUT")
    color("Red")
      translate([0,0,0])
        beam_head_cut_impl3(head1);
  *if(head2!="UNCUT")
    color("Red")
      translate([length,0,0])
        mirror([1,0,0])
          beam_head_cut_impl3(head2);
}

translate([0,0,0]) {
// Show Z-fight
  color("red") {
    translate([-500,-500+beam_width/2,-1000]) cut_beam("OUT",1000,"OUT");
    translate([-500,500-beam_width/2,-1000]) cut_beam("IN",1000,"IN");
  }
  color("green") rotate(90,[0,0,1]) {
    translate([-500,-500+beam_width/2,-1000-(beam_gap+beam_height)/2]) cut_beam("OUT",1000,"OUT");
    translate([-500,500-beam_width/2,-1000-(beam_gap+beam_height)/2]) cut_beam("IN",1000,"IN");
  }
  color("blue") rotate(90,[0,0,1]) {
    translate([-500,-500+beam_width/2,-1000+(beam_gap+beam_height)/2]) cut_beam("OUT",1000,"OUT");
    translate([-500,500-beam_width/2,-1000+(beam_gap+beam_height)/2]) cut_beam("IN",1000,"IN");
  }
// Uncut beam
  translate([0,0,-3000]) cut_beam("UNCUT",1000,"UNCUT"); // uncut beam
// Beams with cut head
  color("white") {
    translate([0,0,-3250]) cut_beam("IN",1000,"UNCUT");  // cut type #1
    translate([0,0,-3500]) cut_beam("OUT",1000,"UNCUT"); // cut type #2
  }
// Elements used to cut beams, remove stars to show the beam to be cut
  translate([0,-500,-3250]) {
    color("red") beam_head_cut_impl3("IN");  // cut piece #1
    *beam(1000);
  }
  translate([0,-500,-3500]) {
    color("red") beam_head_cut_impl3("OUT"); // cut piece #2
    *beam(1000);
  }
}

missing_surfaces
z-fight

@jordanbrown0
Copy link
Contributor

Hints for bug reports:

  • Make as simple a demonstration as possible. For instance, your shaped beam does not affect the missing-face behavior; you can demonstrate the behavior with a simple cube.
  • One problem per report.

With respect to the missing faces...

This one is a new one on me. Your beam_head_cut_impl3 polyhedra have reversed faces. Faces should always have their vertexes listed clockwise, as viewed from outside the polyhedron. Normally reversed faces are only a problem for full F6 render, but they apparently throw difference() for a loop too. Here's a very simple demonstration, differencing two cubes where one of them is a polyhedron with one face reversed.

points = [
    [0,0,0],
    [0,0,1],
    [0,1,0],
    [0,1,1],
    [1,0,0],
    [1,0,1],
    [1,1,0],
    [1,1,1],
];
faces = [
    [1,3,2,0],  //backwards
    [4,0,1,5],
    [6,4,5,7],
    [6,7,3,2],
    [1,3,7,5],
    [4,6,2,0]
];

difference() {
    cube(1);
    translate([0.5,0.5,0.5]) polyhedron(points, faces);
}

In your beam_head_cut_impl3 objects, some of the faces are backwards.

Get rid of all of the coloring, and use the "Thrown Together" view. You want to see all yellow faces. Any purple faces are bad. Usually, fixing that is just a matter of reversing the vertex list, but the way that you've constructed the two polyhedra, one is inside-out from the other.

I haven't tried to deeply understand how you are constructing those polyhedra, but a brute force answer is to first get one of them correct, and then reverse the vertex lists for the other.

Here's a face list that gets the top object to be correct:

    faces = [
        [0,1,2,3],
        [6,7,3,2],
        [4,5,1,0],
        [0,3,7,4],
        [5,6,2,1],
        [7,6,5,4],
    ];

Here's a pair of functions that will reverse vertex lists:

function reverse(a) = [ for (i=[0:len(a)-1]) a[len(a)-1-i] ];
function reverse_faces(faces) = [ for (face=faces) reverse(face) ];

And here's the invocation:

        polyhedron(concat(points_hi,points_cut_face+delta),faces,convexity=10);
        polyhedron(concat(points_low,points_cut_face-delta),reverse_faces(faces),convexity=10);

@UBaer21
Copy link
Contributor

UBaer21 commented Aug 11, 2022

image

F12 "thrown together" view shows wrong normals in faces in magenta (flipped faces) -- As you can see your "cutter" has wrong faces.

@UBaer21
Copy link
Contributor

UBaer21 commented Aug 11, 2022

This fixes your problem

 // line 39
  faces = [
    [3,2,1,0],
    [2,3,7,6],
    [5,4,0,1],
    [4,7,3,0],
    [1,2,6,5],
    [4,5,6,7],
    ];
//line 75
polyhedron(concat(points_cut_face+delta,points_hi),faces,convexity=10);
// line 91
polyhedron(concat(points_cut_face+delta,points_hi),faces,convexity=10);

@Teraslilja
Copy link
Author

Thanx for the tip! I didn't know that only that mode wrong normals can be visualized, my expectation were that wrong normals cause face to vanish in preview mode .. O_o

I fixed the code as suggested, but there is still that Z-fight left for cut surfaces.
Is the cut surfaces expected to be bluish (in preview mode)?

beam_height = 150;
beam_width = 103;
beam_wall_thickness = 10;
beam_coupling = 12;
beam_gap = 12-beam_coupling;

function reverse(a) = [ for (i=[0:len(a)-1]) a[len(a)-1-i] ];
function reverse_faces(faces) = [ for (face=faces) reverse(face) ];

module shaped_beam(length, profile) {
  rotate(90,[0,1,0])
    rotate(90,[0,0,1])
      linear_extrude(height = length,convexity=10)
        polygon(points = profile);
}

module beam(length, extra=0) {
  profile = [
    [-beam_width/2,-beam_height/2],
    [-beam_width/2,beam_height/2],
    [beam_wall_thickness-beam_width/2,beam_height/2],
    [beam_wall_thickness-beam_width/2,beam_height/2+beam_coupling],
    [beam_width/2-beam_wall_thickness,beam_height/2+beam_coupling],
    [beam_width/2-beam_wall_thickness,beam_height/2],
    [beam_width/2,beam_height/2],
    [beam_width/2,-beam_height/2],
    [beam_width/2-beam_wall_thickness,-beam_height/2],
    [beam_width/2-beam_wall_thickness,beam_coupling-beam_height/2],
    [beam_wall_thickness-beam_width/2,beam_coupling-beam_height/2],
    [beam_wall_thickness-beam_width/2,-beam_height/2],
    [-beam_width/2,-beam_height/2],
  ];
  shaped_beam(length, profile);   
}

module beam_head_cut_impl3(cut) {
  extra = 25;
  middle_head_z = [0,0,((beam_gap-beam_coupling)/2+beam_coupling)/2-0.5];
  faces = [
    [0,1,2,3],
    [6,7,3,2],
    [4,5,1,0],
    [0,3,7,4],
    [5,6,2,1],
    [7,6,5,4],
  ];
  points_hi = [
    [-extra,-extra,beam_height+beam_coupling+extra],
    [-extra,+beam_width+extra,beam_height+beam_coupling+extra],
    [+beam_width,+beam_width+extra,beam_height+beam_coupling+extra],
    [+beam_width,-extra,beam_height+beam_coupling+extra],
  ];
  points_low = [
    [-extra,-extra,-extra],
    [-extra,+beam_width+extra,-extra],
    [+beam_width,+beam_width+extra,-extra],
    [+beam_width,-extra,-extra],
  ];
  points_cut_face = [
    [0,0,(beam_height+beam_coupling)/2],
    [0,+beam_width,(beam_height+beam_coupling)/2],
    [+beam_width,+beam_width,(beam_height+beam_coupling)/2],
    [+beam_width,0,(beam_height+beam_coupling)/2],
  ];
  if(cut=="IN") {
    delta = [
      middle_head_z+[0,0,+45],
      middle_head_z+[0,0,+35],
      middle_head_z+[0,0,+25],
      middle_head_z+[0,0,+35],
    ];
    translate([0,-beam_width/2,-beam_height/2])
      union() {
        polyhedron(concat(points_hi,points_cut_face+delta),faces,convexity=10);
        polyhedron(concat(points_low,points_cut_face-delta),reverse_faces(faces),convexity=10);
      }
    echo(" IN: high: ",points_cut_face+delta);
    echo(" IN:  low: ",points_cut_face-delta);
  } else if(cut=="OUT")
  {
    delta = [
      middle_head_z+[0,0,+35],
      middle_head_z+[0,0,+45],
      middle_head_z+[0,0,+35],
      middle_head_z+[0,0,+25],
    ];
    translate([0,-beam_width/2,-beam_height/2])
      union() {
        polyhedron(concat(points_hi,points_cut_face+delta),faces,convexity=10);
        polyhedron(concat(points_low,points_cut_face-delta),reverse_faces(faces),convexity=10);
      }
    echo("OUT: high: ",points_cut_face+delta);
    echo("OUT:  low: ",points_cut_face-delta);
  }
}

module cut_beam(head1,length,head2) {
  difference() {
    beam(length);
    if(head1!="UNCUT")
      translate([0,0,0])
        beam_head_cut_impl3(head1);
    if(head2!="UNCUT")
      translate([length,0,0])
        mirror([1,0,0])
          beam_head_cut_impl3(head2);
  }
  *if(head1!="UNCUT")
    translate([0,0,0])
      beam_head_cut_impl3(head1);
  *if(head2!="UNCUT")
    translate([length,0,0])
      mirror([1,0,0])
        beam_head_cut_impl3(head2);
}

translate([0,0,0]) {
// Show Z-fight
  color("Red") {
    translate([-500,-500+beam_width/2,-1000]) cut_beam("OUT",1000,"OUT");
    translate([-500,500-beam_width/2,-1000]) cut_beam("IN",1000,"IN");
  }
  color("Green")
    rotate(90,[0,0,1]) {
      translate([-500,-500+beam_width/2,-1000-(beam_gap+beam_height)/2]) cut_beam("OUT",1000,"OUT");
      translate([-500,500-beam_width/2,-1000-(beam_gap+beam_height)/2]) cut_beam("IN",1000,"IN");
    }
  color("Blue")
    rotate(90,[0,0,1]) {
      translate([-500,-500+beam_width/2,-1000+(beam_gap+beam_height)/2]) cut_beam("OUT",1000,"OUT");
      translate([-500,500-beam_width/2,-1000+(beam_gap+beam_height)/2]) cut_beam("IN",1000,"IN");
    }
// Uncut beam
  translate([0,0,-3000]) cut_beam("UNCUT",1000,"UNCUT"); // uncut beam
// Beams with cut head
  {
    translate([0,0,-3250]) cut_beam("IN",1000,"UNCUT");  // cut type #1
    translate([0,0,-3500]) cut_beam("OUT",1000,"UNCUT"); // cut type #2
  }
// Elements used to cut beams, remove stars to show the beam to be cut
  translate([0,-500,-3250]) {
    beam_head_cut_impl3("IN");  // cut piece #1
    *beam(1000);
  }
  translate([0,-500,-3500]) {
    beam_head_cut_impl3("OUT"); // cut piece #2
    *beam(1000);
  }
}

z-fight2
blue-cut-surfaces

@jordanbrown0
Copy link
Contributor

With respect to the Z-fighting...

The previewer really hates coincident faces that are different colors - even if those coincident faces have been cut away.

(This makes perfect sense if you look at how the previewer works under the covers... but doesn't make much sense if you don't.)

Here's a simple demonstration, cut down from your example:

color("red") cube(100);

color("blue")  {
    translate([0, 50, 75]) difference() {
        cube(100);
        translate([-50,-125,-125]) cube(200);
    }
}

image

So what can you do about it? Avoiding the coincident faces isn't practical here. But you can (at some performance cost in some cases) work around it by slapping a render() around the difference:

color("red") cube(100);

color("blue")  {
    translate([0, 50, 75]) render() difference() {
        cube(100);
        translate([-50,-125,-125]) cube(200);
    }
}

That takes the differencing out of the hands of the previewer and puts it into the hands of the full renderer, which will well and truly get rid of the subtracted-away parts of the object.

@jordanbrown0
Copy link
Contributor

Another workaround is to make the faces be just a teensy bit non-coincident. Make the beams be just a little shorter.

This seems to be enough:

module shaped_beam(length, profile) {
  rotate(90,[0,1,0])
    rotate(90,[0,0,1])
      translate([0,0,0.1]) linear_extrude(height = length-0.2, convexity=10)
        polygon(points = profile);
}

image

Of course, there you're actually modifying the model, and that's not really desirable. But sometimes it's OK.

@thehans thehans closed this as not planned Won't fix, can't repro, duplicate, stale Aug 11, 2022
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

No branches or pull requests

4 participants