Camera Object Cloning Issue - Same as new with no arguments

Cloning a camera object (or using “dup”) is not resulting in any data copying, not even shallow copying.

Example:

camera1 = Sketchup::Camera.new( [100, 0, 0], [100, 100, 0], Z_AXIS )
camera2 = camera1.clone
puts camera2.eye, camera2.target

Camera 1 creates position data that establishes a direction of north.

The output is from the puts for camera 2 is:

(0", 0", 1")
(0", 0", 0")

That camera is 1 inch above world origin pointing straight down. That is the same values as calling the class’s “new” constructor with no arguments.

I have created my own clone method to provide the functionality desired:

  def clone_camera( camera )
    return Sketchup::Camera.new(camera.eye, camera.target, camera.up, camera.perspective?, camera.fov)
  end

Observe that I didn’t clone the two positions and up vector. The constructor is properly creating new position objects that don’t reference the point/vector argument data.

Because this is an very old known issue. (I myself reported it years ago, and it comes up several times each year.)

Check the API methods index
http://www.sketchup.com/intl/en/developer/docs/methods#index_c
and you’ll see that the API only creates (overrides) 3 methods to safely clone API objects.

Also, only Group has a copy() method that also has had some issues, like not copying attached attribute dictionaries.

Using the standard Ruby clone() or dup() methods upon API objects is problematic and unreliable, unless it is a specially crafted API method that overrides the plain-Jane method inherited from the Object class.

If you stop and think about it, for a minute, … what does Object know about cloning subclass objects, especially complex API objects ? Answer,… Nothing, unless there is something that it can use for instructions on how to copy objects. In Ruby, this is a special method named #initialize_copy(), which must be defined in the subclass, or else the basic empty one inherited from class Module (down through Kernel and Object,) will be used. It basically will just call the a constructor upon the class in question, as if it was called from within that class’ new constructor as super() (without arguments.)

Sketchup::Camera.instance_method(:initialize).owner
>> Sketchup::Camera

No problem there. The API camera class defines it’s own initialize method so as to create objects that the API can use for cameras. (This gets automatically called by the Ruby interpreter at the end of the new constructor’s processing.)

Sketchup::Camera.instance_method(:initialize_copy).owner
>> ::Kernel

This shows that the API Camera class does not define a unique initialize_copy method to instruct standard Ruby clone() or dup() method calls on how to copy a camera.

ADD: The same holds true for the two other callbacks initialize_clone() and initialize_dup() which by default only call initialize_copy() for common processing, but can be overridden to provide differing copy processing.


Fixing this issue is apparently low priority because the workaround is simple,… do not use these standard Ruby methods.

def clone_camera(cam)
  Sketchup::Camera::new(
    cam.eye,
    cam.target,
    cam.up,
    cam.perspective?,
    cam.fov
  )
end

Also, any singleton class object should never be cloned or duped. (Ie, manager type collection objects.)
Get an array copy of them instead, using their to_a() method (if they have one defined.)


The terms shallow and deep copying are usually in regard to data collection classes that can be nested like Array, Hash or AttributeDictionary, and that hold references to other objects.

If you clone an array, you get a new Array object, that has the same references to the same objects as the original array.

Shallow copy” cannot apply to Sketchup::Camera objects as it’s instance methods always return new “property” objects for subsequent calls to “property” instance methods. (Ie, each time you call cam.eye a new Geom::Point3d object is created and returned.)


FYI, the Ruby standard library’s REXML module has a Array deep copy recursive method you can steal and modify for your extensions, … but it may need fixing (because it relies upon adding other clone methods to standard classes that cannot be cloned, like Integer, etc.):

# File: "#{lib}/rexml/xpath_parser.rb", line 32
def dclone
  klone = self.clone
  klone.clear
  self.each{|v| klone << v.dclone}
  klone
end

I’d fix the block and make a method for hashes:

# Array deep clone
def deep_clone_array(ary)
  klone = ary.clone
  klone.clear
  ary.each {|obj|
    klone << if obj.is_a?(Array)
      deep_clone_array(obj)
    elsif obj.is_a?(Hash)
      deep_clone_hash(obj)
    elsif obj.respond_to?(:clone)
      obj.clone rescue obj
    else
      obj
    end
  }
  klone
end

# Hash deep clone:
def deep_clone_hash(hsh)
  klone = hsh.clone
  klone.clear
  hsh.each {|key,obj|
    klone[key]= if obj.is_a?(Array)
      deep_clone_array(obj)
    elsif obj.is_a?(Hash)
      deep_clone_hash(obj)
    elsif obj.respond_to?(:clone)
      obj.clone rescue obj
    else
      obj
    end
  }
  klone
end

:geek: