View transform origin
When transforming a view (not a scene), what is the origin, and can I change it?
Replying to myself:
Origin is the center, as stated in the docs. Have not found any method to change this.
More view transform confusion: rotation seems an absolute transform (sets a specific angle, not incremental), while translate is relative to current position. Confusion about this made also the result of invert() surprising - expected invert to return the view to the original position, which it did not, as there was rotation involved.
I kindly request pointing this out in the docs.
If you want incremental transforms, you use concat. If you just set your view.transform to a new transform, it completely overrides any existing transform, and so you end up with a transform relative to the original view location. Note that rotations, and possibly scale, are relative to the original view center, so if you have a translation currently, then concat a rotation, you rotate with a lever arm in place. if you want to rotate in place, you have to apply the rotation before the current transform.
Here is a little playground that shows that setting transform always results in an absolute transform from the original view. By using concat, you can use incremental. I also show a few contrived examples where invert is useful, and an example where the rotation origin is changed - which involves a translation, rotation, then inverted original translation.
# coding: utf-8 # ui.Transform playground # discover that all transforms are absolute and override existing transform, unless using concat #. also, discover use for invert, such as changing order of transforms import ui from math import pi v=ui.View(frame=(0,0,700,700),bg_color='white') target=ui.Label(bg_color=(.5,.5,.5)) target.text='Target' target.center=500,200 crosshair=ui.Label() crosshair.text='+' crosshair.size_to_fit() crosshair.center=500,200 v.add_subview(target) v.add_subview(crosshair) #define some actions which operate on target def translate_absolute(sender): target.transform=ui.Transform.translation(100,0) def translate_relative(sender): target.transform=target.transform.concat(ui.Transform.translation(100,0)) def rotation_relative(sender): target.transform=target.transform.concat(ui.Transform.rotation(pi/8.)) def rotation_absolute(sender): target.transform=ui.Transform.rotation(pi/8.) def rotation_relative_in_place(sender): # invert current transform, rotate, then redo transform inverted_then_rotation_then_transform= target.transform.invert().concat( ui.Transform.rotation(pi/8.)).concat( target.transform) target.transform=target.transform.concat(inverted_then_rotation_then_transform) def rotate_about_top_r_corner(sender): #first, we shift by -w/2,-h/2 to get tr corner at rot center # then rotate # then shift back so corner matches # then spply existing transform t=ui.Transform.translation(-target.width/2.,target.height/2.) r=ui.Transform.rotation(pi/8.) target.transform=t.concat(r).concat(t.invert()).concat(target.transform) # build the buttons buttons=[translate_absolute, translate_relative, rotation_absolute, rotation_relative, rotation_relative_in_place, rotate_about_top_r_corner] y=400 for b in buttons: btn=ui.Button(title=b.__name__) btn.action=b btn.y=y btn.x=50 btn.height=30 btn.border_width=1 btn.bg_color=(1,.7,.7) y+=45 v.add_subview(btn) v.present()
Thanks for a truly impressive reply. Would be excellent to have examples like these included in the documentation.
I tried to clear transforms by setting view.transform to None, but that did not work, so I set it to translation(0, 0) instead. Is there a "proper" way to clear the transforms?
You can also use
ui.Transform()to get the "identity" transformation (though effectively,
translation(0, 0)does the same thing).