Heal the klein bagel

Hello again,

I decided to try to render this thing called a ‘Klein Bagel’ Klein bottle - Wikipedia

Here’s the resulting skp file
klein_bagel.skp (1.6 MB)

Plotting the edges is easy. The hardest part is figuring out how to create the faces so it looks like a surface.

Here is the script

module FooKleinBagel

  def self.draw_edges(entities, start, limit, scale_factor, facet_size)
    points = []
    edges = []
    cycles = []
   
    i = 0
    last_point = nil
  
    r = 3
    for theta in (start..limit).step(facet_size)
      cycle = []
      for v in (0..2*Math::PI).step(facet_size)
        half_theta = theta / 2.0
        sinv = Math.sin(v)
        sin2v = Math.sin(2*v)
        cos_theta = Math.cos(theta)
        sin_theta = Math.sin(theta)
        cos_half_theta = Math.cos(half_theta)
        sin_half_theta = Math.sin(half_theta)
        
        part1 = (r + (cos_half_theta * sinv) - (sin_half_theta * sin2v)) 
        
        x = part1 * cos_theta * scale_factor
        y = part1 * sin_theta * scale_factor
        z = ((sin_half_theta * sinv) + (cos_half_theta * sin2v)) * scale_factor
        
        point = Geom::Point3d.new(x,y,z)
        points << point
        if last_point
          edge = entities.add_line(last_point, point)
          edges << edge
          cycle << edge
        end
        last_point = point
        i += 1
      end # for v
      cycles << cycle
    end # for theta
    return [points, edges, cycles]
  end
  
  def self.connect_with_faces(entities, cycle1, cycle2, reverse_start_and_end=FALSE)
    cycle1.each_with_index { |edge, i|
      edge2 = cycle2[i]
      if edge2.nil?
        next
      end
      v1 = edge2.start.position
      v2 = edge2.end.position
      v3 = edge.start.position
      v4 = edge.end.position
      if reverse_start_and_end
        v1 = edge2.start.position
        v2 = edge2.end.position
        v3 = edge.end.position
        v4 = edge.start.position
      end
      pts1 = [v1,v2,v3].uniq(&:to_a)
      if pts1.length > 2
        face1 = entities.add_face(pts1)
      else
        print("i: #{i}, non unique: #{pts1}")
      end
      pts2 = [v2,v3,v4].uniq(&:to_a)
      if pts2.length > 2
        face2 = entities.add_face(pts2)
      else
        print("i: #{i}, non unique: #{pts2}")
      end
    } # end for each edge in cycle
  end # method connect_with_faces 
  
  def self.draw_surfaces(entities, cycles)
    last_cycle = cycles[0]
    first_cycle = last_cycle
    cycles = cycles.drop(1)
    cycles.each { |cycle|
      self.connect_with_faces(entities, cycle, last_cycle)
      last_cycle = cycle
    }
    # now connect up the end to the beginning to complete it
    # reverse the order of the edges for the first_cycle because we're all twisted around
    rev_cycle = first_cycle.reverse
    self.connect_with_faces(entities, last_cycle, rev_cycle, TRUE)
  end # method: draw_surfaces
end # module FooKleinBottle

model = Sketchup.active_model
ents = model.active_entities

scale_factor = 100
facet_size = 0.1
start = 0
limit = 2 * Math::PI

model.start_operation('draw_edges', true)

    edge_group = ents.add_group
    entities = edge_group.entities

     points, edges, cycles = FooKleinBagel.draw_edges(entities, start, limit, scale_factor, facet_size)

model.commit_operation

model.start_operation('draw_surfaces', true)

  face_group = ents.add_group
  
  FooKleinBagel.draw_surfaces(face_group.entities, cycles)
  
model.commit_operation

I came up with this approach to doing it, however, in the last step, I have to connect the first cycle up to the last one by reversing the first cycle and stitching them together with faces. However, there’s a glitchy hole created in this last step.

Is there an easier way to connect the edges with surfaces?

Thank you for any thoughts
-j_jones

1 Like

How about ?

edges.each do |edge|
  edge.find_faces
  edge.soft= true
  edge.smooth= true
end
1 Like

There was a tiny error in the way the edge loops were being created. Here I’ve changed the draw_edges() method to isolate each cycle from its neighbors.

module FooKleinBagel

  def self.draw_edges(entities, start, limit, scale_factor, facet_size)
    points = []
    edges = []
    cycles = []
   
    i = 0
    #last_point = nil
  
    r = 3
    for theta in (start..limit).step(facet_size)
      cycle = []
      first_point = nil
      last_point = nil

      for v in (0..2*Math::PI).step(facet_size)
        half_theta = theta / 2.0
        sinv = Math.sin(v)
        sin2v = Math.sin(2*v)
        cos_theta = Math.cos(theta)
        sin_theta = Math.sin(theta)
        cos_half_theta = Math.cos(half_theta)
        sin_half_theta = Math.sin(half_theta)
        
        part1 = (r + (cos_half_theta * sinv) - (sin_half_theta * sin2v)) 
        
        x = part1 * cos_theta * scale_factor
        y = part1 * sin_theta * scale_factor
        z = ((sin_half_theta * sinv) + (cos_half_theta * sin2v)) * scale_factor
        
        point = Geom::Point3d.new(x,y,z)
        points << point
        if first_point
          edge = entities.add_line(last_point, point)
          edges << edge
          cycle << edge
        else
          first_point = point
        end
        last_point = point
        i += 1
      end # for v
      
      edge = entities.add_line(last_point, first_point)
      edges << edge
      cycle << edge
      
      cycles << cycle
    end # for theta
    return [points, edges, cycles]
  end
  
  def self.connect_with_faces(entities, cycle1, cycle2, reverse_start_and_end=FALSE)
    cycle1.each_with_index { |edge, i|
      edge2 = cycle2[i]
      if edge2.nil?
        next
      end
      v1 = edge2.start.position
      v2 = edge2.end.position
      v3 = edge.start.position
      v4 = edge.end.position
      if reverse_start_and_end
        v1 = edge2.start.position
        v2 = edge2.end.position
        v3 = edge.end.position
        v4 = edge.start.position
      end
      pts1 = [v1,v2,v3].uniq(&:to_a)
      if pts1.length > 2
        face1 = entities.add_face(pts1)
      else
        print("i: #{i}, non unique: #{pts1}")
      end
      pts2 = [v2,v3,v4].uniq(&:to_a)
      if pts2.length > 2
        face2 = entities.add_face(pts2)
      else
        print("i: #{i}, non unique: #{pts2}")
      end
    } # end for each edge in cycle
  end # method connect_with_faces 
  
  def self.draw_surfaces(entities, cycles)
    last_cycle = cycles[0]
    first_cycle = last_cycle
    cycles = cycles.drop(1)
    cycles.each { |cycle|
      self.connect_with_faces(entities, cycle, last_cycle)
      last_cycle = cycle
    }
    # now connect up the end to the beginning to complete it
    # reverse the order of the edges for the first_cycle because we're all twisted around
    rev_cycle = first_cycle.reverse
    self.connect_with_faces(entities, last_cycle, rev_cycle, TRUE)
  end # method: draw_surfaces
end # module FooKleinBottle

model = Sketchup.active_model
ents = model.active_entities

scale_factor = 100
facet_size = 0.1
start = 0
#limit = 1.0/16.0 * Math::PI
limit = 2 * Math::PI

model.start_operation('draw_edges', true)

    edge_group = ents.add_group
    entities = edge_group.entities

     points, edges, cycles = FooKleinBagel.draw_edges(entities, start, limit, scale_factor, facet_size)

model.commit_operation

model.start_operation('draw_surfaces', true)

  face_group = ents.add_group
  
  FooKleinBagel.draw_surfaces(face_group.entities, cycles)
  
model.commit_operation
1 Like

Thank you

I wish the find_faces would have worked, but I guess the edges were too ambiguous for it to just guess.

But the changes suggested by sWilliams did the trick, thank you!

1 Like

Just wanted to show off this stripedy version

3D Warehouse.

I found out the hard way that there is a limit to the number of materials you can use before poor sketchup gets very bogged down indeed.

I still have a little glitch to fix, but I will re-upload the correction soon.

glitch fixed!

2 Likes