Copy move confusion

I am having some confusion, I’m hoping someone can help me.

I want to make a stack of copies of this world map and give each layer a time period.

However, when I copy the map and modify the time period, the time period isn’t lining up correctly.

If I try to delete the time from the previous map, the time period still isn’t lining up.

Here is the sketchup file containing a world map I found in the warehouse
world_map.skp (1.0 MB)

Thank you for any and all clues!
-j_jones

Here is my script so far

module JJones
  
  def self.make_color(model,name,r,g,b,rgb_alpha=255, material_alpha=1.0)
    color = Sketchup::Color.new(r,g,b,rgb_alpha)
    material = model.materials.add("#{name} #{r} #{g} #{b}")
    material.color = color
    material.alpha = material_alpha
    return material
  end # make_color
  
  def self.make_text(group, text, margin, txt_size=5)
    print("make_text: #{text}")
    tgroup = group.entities.add_group
    #str, alignment, font, is_bold, is_italic, letter_height, tolerance, z, is_filled, extrusion)
    txt = tgroup.entities.add_3d_text(text, TextAlignCenter, "Arial", true, false, txt_size, 0.0, 0.0, true, 0.25)
  
    # move text to right side of group
    gbounds = group.bounds
    rfb_corner = gbounds.corner(1)
    x,y,z = rfb_corner.to_a
    target = Geom::Point3d.new(x+margin,y,z)
    bounds = tgroup.bounds
    xt = Geom::Transformation.translation(bounds.center.vector_to(target))
    tgroup.transform!(xt)
    tgroup.name = text
    tgroup
  end # make_text
  
  def self.create_spectrum(rgb_alpha=255, material_alpha=1.0)
    model = Sketchup.active_model
    colors = {}
    color_array = []
    color_name = "magenta"
    material = self.make_color(model, color_name, 135,63,191, rgb_alpha, material_alpha) #0
    colors[color_name] = material
    color_array << material
  
    color_name = "pink"
    material = self.make_color(model, color_name, 191,63,186, rgb_alpha, material_alpha) #1
    colors[color_name] = material
    color_array << material
  
    color_name = "red"
    material = self.make_color(model, color_name, 191,63,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "orange"
    material = self.make_color(model, color_name, 191,124,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "yellow"
    material = self.make_color(model, color_name, 191,186,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "green"
    material = self.make_color(model, color_name, 73,191,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "turquoise"
    material = self.make_color(model, color_name, 63, 191, 160, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "sky blue"
    material = self.make_color(model, color_name, 63, 191, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "blue"
    material = self.make_color(model, color_name, 63, 114, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "dark blue"
    material = self.make_color(model, color_name, 63, 68, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "purple"
    material = self.make_color(model, color_name, 89, 63, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
    return colors, color_array
  end # create_spectrum
  
  def self.get_group_color(group)
    faces = group.definition.entities.grep(Sketchup::Face)
    f = faces[0]
    f.material
  end # get_group_color
  
  def self.get_year_txt(year)
    year_txt = year.to_s + ((year < 0) ? " BCE" : " CE")
  end # get_year_txt
  
  def self.print_corners(bounds)
    print("bounds corners (left front bottom)  [0,0,0] : #{bounds.corner(0)}")
    print("bounds corners (right front bottom) [1,0,0] : #{bounds.corner(1)}")
    print("bounds corners (left back bottom)   [0,1,0] : #{bounds.corner(2)}")
    print("bounds corners (right back bottom)  [1,1,0] : #{bounds.corner(3)}")
    print("bounds corners (left front top)     [0,0,1] : #{bounds.corner(4)}")
    print("bounds corners (right front top)    [1,0,1] : #{bounds.corner(5)}")
    print("bounds corners (left back top)      [0,1,1] : #{bounds.corner(6)}")
    print("bounds corners (right back top)     [1,1,1] : #{bounds.corner(7)}")
  end # print_corners
  
  def self.main(millennia, inter_map_distance, start_year, year_margin, erase_prior_year)
    
    model = Sketchup.active_model
    entites = model.active_entities
    selection = model.selection
    model.start_operation("copy upwards", true)
    
    colors, color_array = create_spectrum(rgb_alpha=100, material_alpha=0.3)
    num_colors = color_array.length

    year = start_year
    year_txt = get_year_txt(year)
    print("year_txt: #{year_txt}")
    
    if selection.empty?
      UI.messagebox("Please select an object to copy.")
    else
      # select the map, make a copy, add the year, move it up by inter_map_distance
      group = selection[0]

      (0..millennia).each do |i|
        print("millennia: #{i}")
        
        old_color = get_group_color(group)
        print("#{i}, #{year_txt}: old color: #{old_color}, #{old_color.class}, #{old_color.name}")

        # debugging why the years are wacky 
        if erase_prior_year # remove last year text 
          last_year = group.entities.grep(Sketchup::Group).find_all{ |g| g.name==year_txt }
          group.entities.erase_entities(last_year) if last_year[0]
        end # if erase prior year
        
        # add the year to the right side of the map 
        year_txt = get_year_txt(year)
        tgroup = make_text(group, year_txt, year_margin)

        # find front bottom left corner
        bounds = group.bounds
        x,y,z = bounds.corner(0).to_a
        print_corners(bounds)
    
        # calculate new position for the copy
        new_pos = [x,y,z+inter_map_distance]
        print("#{i}, #{year_txt}: new_pos: #{new_pos}")

        # copy and move the map up to new position
        transform = Geom::Transformation.new(new_pos)
        new_group = entites.add_instance(group.definition, transform)
        new_group.make_unique
        
        # give new map a different color
        color = color_array[i % num_colors]
        new_group.material = color
        faces = new_group.definition.entities.grep(Sketchup::Face)
        faces.each do |f|
          f.material = color
          f.back_material = color
        end
    
        year += 1000
        group = new_group
      end # each millennia
        
    end # if selection
    model.commit_operation
  end # main
end # module

millennia=3
inter_map_distance=36
year_margin=15
start_year=-14000
erase_prior_year=TRUE

JJones.main(millennia, inter_map_distance, start_year, year_margin, erase_prior_year)

Blockquote

I’m not sure where you want the text to be. But it it printing it where you are telling it to. Your comment is out of order from your code; Your code goes:
select the map, add the year, make a copy, move it up by inter_map_distance.

So, your text is transformed from the previous copy’s bounds.corner(1)'s coordinates. Since your first map’s bounds.corner(1).z is at 0, the translation does not move the text up and so coincidentally, your text ends up on the same plane as your first map and it looks like its working. The second map is then moved up 100" (8’-4") 36" and its bounds.corner(1).z is now at 8’-4" 36". You then tell your text to move to that distance from the maps bounds.center. That’s why then second map’s text is 8’-4" 36"above the second map, as should be evident in the print out from your method, print_corners (is it?). The rest of the maps and text follow suit.

Edit: I couldn’t find the Text Strikethrough so I blured the erroneous text instead. I changed my original 100" (8’-4") to 36" three (3) times.

1 Like

Thank you for responding. I see that comment is in fact out of sync with the code. I was swapping things around trying to get the years to line up with the maps and forgot to update that comment.

I’ll have to look more closely at your suggestions when I get home, but basically I want to make a copy of the map and add the year (-1000) along side it on the right, then move the pair of items (map and year) up by the inter_map_distance, which in my example would be 3’ or 36".

So ultimately, I would get a stack of maps extending upwards, each map representing an “era” of the world around that time.

I guess I was expecting the text to be inside the map group since I added it using the map group’s entities and therefore I thought they would move together the same distance, but as you can see, the text flies way up beyond the level of the map, which is confusing to me, not sure why that’s happening.

Thank you again for responding and I’ll look more closely this evening when I’m back in front of my dedicated Sketchup computer.

I think I should delete this topic as it isn’t well worded or focused on the true problem…

Wait. I may have confused you. As I played with your code, I changed the inter_map_distance to 100 to space out the maps so I could see what was going on and forgot that I did do by the time I replied to your post. Subsequently I commented that you move the second map up by 100 instead of 36. I will edit my post to reflect your code, not mine. My apologies if I confused you. Either way, my observations hold. The order of events in your code affect the relative position of the text to the map.

I can get your code to work and I think you will get it too once you see and understand the transformation ‘error’ I was pointing out and can move beyond it. Hope this helps.

I’d appreciate it if you don’t delete the topic as I spent time to answer your question and I’d like to see you get it. Lets stick with this. Your code is clear and your post is clear too. I’d rather you work it out then just outright give you the answer. Your almost there. You’ll get it, I can tell.

2 Likes

Thank you, alright…

Ok, well, usually I am more thoughtful about example code I post, but this time I was lazy. I think Covid may have eaten my brain, not sure. Anyway, there’s too much going on in that code that is extraneous to the isssue, and a lot of the variable names are vague which added to the confusion, and the code is generally a shameful mess, but I digress.

I see what you mention where I was using the bounds.center instead of the corner, but even after correcting that, the text still flies up for me…

So, I think I over complicated it. What I wanted to do was (idea 1) copy a grouped thing, add a thing to the copied group, then move the copied group up, and then iterate on that n times.

So I realized that I can just do it in two separate iterations (idea 2), as illustrated below. And sure enough, that solves the “problem”. I still don’t quite understand why the text wants to fly higher up than the map after the move in (idea 1), but in the interest of getting on with life, I decided to just take the easier route (idea 2) shown below.

(idea 2) revised script:

module JJones
  
  def self.make_color(model,name,r,g,b,rgb_alpha=255, material_alpha=1.0)
    color = Sketchup::Color.new(r,g,b,rgb_alpha)
    material = model.materials.add("#{name} #{r} #{g} #{b}")
    material.color = color
    material.alpha = material_alpha
    return material
  end
  
  def self.make_year_text(start_corner, tgroup, text, margin, txt_size=5)
    #str, alignment, font, is_bold, is_italic, letter_height, tolerance, z, is_filled, extrusion)
    txt = tgroup.entities.add_3d_text(text, TextAlignCenter, "Arial", true, false, txt_size, 0.0, 0.0, true, 0.25)
    
    tbounds = tgroup.bounds
    tcorner = tbounds.corner(0)
    
    # position text
    x,y,z = start_corner
    target = Geom::Point3d.new(x+margin,y,z)
    xt = Geom::Transformation.translation(tcorner.vector_to(target))
    tgroup.transform!(xt)
    tgroup.name = text
    tgroup
  end
  
  def self.create_spectrum(rgb_alpha=255, material_alpha=1.0)
    model = Sketchup.active_model
    colors = {}
    color_array = []
    color_name = "magenta"
    material = self.make_color(model, color_name, 135,63,191, rgb_alpha, material_alpha) #0
    colors[color_name] = material
    color_array << material
  
    color_name = "pink"
    material = self.make_color(model, color_name, 191,63,186, rgb_alpha, material_alpha) #1
    colors[color_name] = material
    color_array << material
  
    color_name = "red"
    material = self.make_color(model, color_name, 191,63,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "orange"
    material = self.make_color(model, color_name, 191,124,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "yellow"
    material = self.make_color(model, color_name, 191,186,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "green"
    material = self.make_color(model, color_name, 73,191,63, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "turquoise"
    material = self.make_color(model, color_name, 63, 191, 160, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "sky blue"
    material = self.make_color(model, color_name, 63, 191, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "blue"
    material = self.make_color(model, color_name, 63, 114, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "dark blue"
    material = self.make_color(model, color_name, 63, 68, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
  
    color_name = "purple"
    material = self.make_color(model, color_name, 89, 63, 191, rgb_alpha, material_alpha)
    colors[color_name] = material
    color_array << material
    return colors, color_array
  end
  
  def self.get_group_color(group)
    faces = group.definition.entities.grep(Sketchup::Face)
    f = faces[0]
    color = f.material
    return color
  end # get_group_color
  
  def self.get_year_txt_string(year)
    year_txt = year.to_s + ((year < 0) ? " BCE" : " CE")
  end # get_year_txt_string
  
  def self.print_corners(label, bounds)
    print("#{label} bounds corners (left front bottom)  [0,0,0] : #{bounds.corner(0)}")
    print("#{label} bounds corners (right front bottom) [1,0,0] : #{bounds.corner(1)}")
    # print("#{label} bounds corners (left back bottom)   [0,1,0] : #{bounds.corner(2)}")
    # print("#{label} bounds corners (right back bottom)  [1,1,0] : #{bounds.corner(3)}")
    print("#{label} bounds corners (left front top)     [0,0,1] : #{bounds.corner(4)}")
    print("#{label} bounds corners (right front top)    [1,0,1] : #{bounds.corner(5)}")
    # print("#{label} bounds corners (left back top)      [0,1,1] : #{bounds.corner(6)}")
    # print("#{label} bounds corners (right back top)     [1,1,1] : #{bounds.corner(7)}")
  end # print_corners
  
  def self.color_group(color, group)
    group.material = color
    faces = group.definition.entities.grep(Sketchup::Face)
    faces.each do |f|
      f.material = color
      f.back_material = color
    end
  end
  
  def self.get_year_group_from_map_group(map_group, year_txt)
    year_txt_group = map_group.entities.grep(Sketchup::Group).find_all{ |g| g.name==year_txt }
    found = nil
    if (year_txt_group && year_txt_group[0])
      found = year_txt_group[0]
    end
    print("find year #{year_txt} in map returning: #{found}")
    found
  end
  
  def self.remove_year_text_from_map_group(map_group, year_txt)
    found = get_year_group_from_map_group(map_group, year_txt)
    map_group.entities.erase_entities(found) if found
  end
  
  def self.main(millennia, inter_map_distance, start_year, year_increment, year_margin)
    
    model = Sketchup.active_model
    entities = model.active_entities
    selection = model.selection
    model.start_operation("copy upwards", true)
    
    colors, color_array = create_spectrum(rgb_alpha=100, material_alpha=0.3)
    num_colors = color_array.length

    if selection.empty?
      UI.messagebox("Please select an object to copy.")
    else
      # select the map, make a copy, color it and move it up by inter_map_distance
      map_group = selection[0]
      last_map_group = map_group

      (0..millennia).each do |i|
        # find front bottom left corner of map
        bounds = last_map_group.bounds
        x,y,z = bounds.corner(0).to_a

        # calculate new position for the copy
        new_pos = [x,y,z+inter_map_distance]

        # copy the map and move it to new position
        transform = Geom::Transformation.new(new_pos)
        new_map_group = entities.add_instance(last_map_group.definition, transform)
        new_map_group.make_unique

        # give new map a different color
        color = color_array[i % num_colors]
        color_group(color, new_map_group)
        print("#{i}, color: #{color.name}")
    
        last_map_group = new_map_group
      end # each millennia
      
      year = start_year
      
      bounds = map_group.bounds
      corner = bounds.corner(1).to_a # [110,0,0] # 9'2: = 90 + 18 + 2 = 110

      (0..millennia+1).each do |i|
        # add the year to the right side of the map
        year_txt = get_year_txt_string(year)
        tgroup = entities.add_group
        tgroup = make_year_text(corner, tgroup, year_txt, year_margin)

        color = color_array[i % num_colors]
        color_group(color, tgroup)
        print("i: #{i}, year_txt: #{year_txt}") 

        corner = [corner[0],corner[1],corner[2] + inter_map_distance]
        year += year_increment
      end
        
    end # if selection
    model.commit_operation
  end # main
end # module

millennia=16
inter_map_distance=36
year_margin=5
year_increment=1000
start_year=-15000

JJones.main(millennia, inter_map_distance, start_year, year_increment, year_margin)

Is there a good reason to be painting the faces with the colors ?

If not, I would have used a single group definition, added a new group at a transformation above the last for each level and put a map instance and a text instance in each level group. Then painted the map group and the text group with the desired level color. This way unique group definitions would not be needed.

Well the colors help match the year up visually with the map, but they are not strictly needed for this example.

I see the basic problem finally, is when I asked for the bounds and corners of the copied object, they are reported with values that are respective to the true origin of the whole model.

However, when I apply transformations to the copied group using those values, they get applied to the copy who thinks the origin exists inside itself, relative to itself and so all the values used are too big, so to speak.

Here I tried again with a simpler script…

I start with a 1’ square cube at the origin as shown

Then after running the script below, it makes copies the more or less the way I expected

module JJones
  
  def self.make_text(parent_group, text, txt_size=5)
    txt_group = parent_group.entities.add_group
    #str, alignment, font, is_bold, is_italic, letter_height, tolerance, z, is_filled, extrusion)
    txt = txt_group.entities.add_3d_text(text, TextAlignCenter, "Arial", true, false, txt_size, 0.0, 0.0, true, 0.25)
    txt_group
  end
  
  def self.find_named_group_in_parent_group(parent_group, name)
    found_groups = parent_group.entities.grep(Sketchup::Group).find_all{ |g| g.name==name }
    found = nil
    if (found_groups && found_groups[0])
      found = found_groups[0]
    end
    found
  end
  
  def self.remove_named_group_from_parent_group(parent_group, name)
    found = find_named_group_in_parent_group(parent_group, name)
    parent_group.entities.erase_entities(found) if found
  end
  
  def self.copy_group_up(entities, obj_group, distance)
    # make a copy of the object and move it up the z axis by distance

    # find front top left corner of object
    obj_bounds = obj_group.bounds
    left_front_top_corner = obj_bounds.corner(4).to_a # left front top corner
    x,y,z = left_front_top_corner.to_a # left front top corner

    # calculate new position
    new_pos = [x,y,z+distance] # move object up the z axis
        
    # copy the group and move it to new position
    transform = Geom::Transformation.new(new_pos)
    new_object_group = entities.add_instance(obj_group.definition, transform)
    new_object_group.make_unique
    new_object_group
  end
  
  def self.make_callout(entities, obj, text, callout_length)
    obj_bounds = obj.bounds
    left_front_top_corner = obj_bounds.corner(4).to_a # left front top corner
    x,y,z = left_front_top_corner.to_a # left front top corner
    print("make_callout: lft corner of obj: #{x},#{y},#{z}")
    # make_callout: lft corner of obj: 0.0,0.0,36.0 # relative to true origin

    callout_pt = Geom::Point3d.new(x-callout_length,y,z)
    x2,y2,z2 = callout_pt.to_a
    print("make_callout: callout_pt: #{x2},#{y2},#{z2}")
    # make_callout: callout_pt: -6.0,0.0,42.0

    v1 = Geom::Vector3d.new(left_front_top_corner, callout_pt)
    callout_txt = entities.add_text(text.to_s, callout_pt, v1)
    callout_txt.leader_type = 2
    callout_txt.display_leader = true
    callout_txt.arrow_type = 3
    callout_txt
  end
  
  def self.main(iterations, margin)
    
    model = Sketchup.active_model
    entities = model.active_entities
    selection = model.selection
    model.start_operation("copy upwards", true)
    
    if selection.empty?
      UI.messagebox("Please select an object to copy.")
    else
      # select the object, make a copy, move it up, update the number of the group
      obj_group = selection[0]

      (1..iterations).each do |i|
        print("i: #{i}")

        # first, copy and move the group
        copy = copy_group_up(entities, obj_group, margin)
        print("i: #{i}, copy: #{copy}")
        
        # add a callout to the new object so we know which one it is
        make_callout(entities, copy, i.to_s, margin/2)

        # delete the previous number from the group copy if it exists
        remove_named_group_from_parent_group(copy, "number")
        
        # create the next number 
        txt_group, text = make_text(copy, i.to_s)
        
        # position the number next to the object on the right side
        corner_rfb = copy.bounds.corner(1)  # right front bottom corner of the object
        x,y,z = corner_rfb.to_a
        print("rfb corner of copy: #{x},#{y},#{z}") # 1: [12,0,24], 2: [12,0,48], 3: [12,0,72]

        target = Geom::Point3d.new(x+margin,y,z) # rfb of object translated along x by margin
        x,y,z = target.to_a
        print("txt move target: #{x},#{y},#{z}") # 1: [24,0,24], 2: [24,0,48], 3: [24,0,72]
        
        target = Geom::Point3d.new(2*margin,0,0)

        corner_txt_lfb = txt_group.bounds.corner(0) # left front bottom corner of the text, always [0,0,0]

        # move the text to be alongside the object
        xt = Geom::Transformation.translation(corner_txt_lfb.vector_to(target))
        txt_group.transform!(xt)
        txt_group.name = "number"

        obj_group = copy
      end # each iteration
        
    end # if selection
    model.commit_operation
  end # main
end # module

iterations=3
margin=12

JJones.main(iterations, margin)

But your code only works for a specific sized object in order for the text to end up alongside the object. It’s absolute code, not relative code. If you make the cube 3’, the “number” group ends up inside of not alongside of the object. Or, if you were to first extrude the 1’ cube to any length for that matter, so that you elongated any face with your corner_rfb, it would be nice if the “number” group still ended up alongside the object at that corner after running your code. Why not make the code work on any object of any size?

You missed the point of my question.

If you paint the instance(s), then any unpainted faces will display using the instance’s material.
In this way, only one definition need be created with unpainted faces, which all maps can share.

Ok well, it was just example code to try to describe a problem I was having. It’s not intended to be all purpose code.