//////////////////////////////////////////////////////////////////////////////// // Note from the Scriptographer.org Team // // In Scriptographer 2.9, we switched to a top-down coordinate system and // degrees for angle units as an easier alternative to radians. // // For backward compatibility we offer the possibility to still use the old // bottom-up coordinate system and radians for angle units, by setting the two // values bellow. Read more about this transition on our website: // http://scriptographer.org/news/version-2.9.064-arrived/ script.coordinateSystem = 'bottom-up'; script.angleUnits = 'radians'; /* rot3d Scriptographer 2.9.064 script Scriptographer is a plugin for Adobe Illustrator(TM) created by Juerg Lehni http://www.scriptographer.org/ This script was written by Hiroyuki Sato http://shspage.blogspot.jp/ 2009-05-24 2009-05-27 *modified applying order: self-rotation > height > global-rotation > global-height *If the parent of the target is an instance of Item ( unless it is not a Layer), -> gets options and applys recursively 2009-05-27a *added L,R,T,B,Z option. see "OPTIONS BY THE NAME OF THE TARGETS" section for detail. *fixed a bug in "getItemSetRect" function 2009-06-30: *removed "MIN_RADIUS" option *added "fix_z_order" option 2009-?-? *official staff modified to work with Sg 2.5. Thanks! 2010-02-21 *modified to work with Sg 2.6 2010-?-? *official staff modified to work with Sg 2.9. Thanks! 2013-02-14 *fixed a bug in Group detection *simplified the code 2013-02-14 *fixed a bug in rotation ----------------------------------------------------------- HOW TO USE: *Select pathes and drag. *Release mouse button to fix the result. OPTIONS: focal length: distance to focal point global depth: default depth (negative value for z-axis) fix z-order: (see OTHER NOTE below) BE CAREFULL NOT TO DO SUBSEQUENT DRAGS: *Once mouse button was released, the targets' z-axis values are set to zero, and they return to 2D plane, as distorted shapes. So, if you select these transformed targets and drag again, the result must be a quite unexpected one. If you don't prefer the result, make UNDO first. OPTIONS BY THE NAME OF THE TARGETS: *This script gets optional values from the name of the object as follows. (N means a number) N: height ( - global depth) (must be at the beginning of its name) xN: x-axis rotation. uses center of the object itself as origin xoN: x-axis rotation. uses global origin (default: center of the selection) yN, zN: same as "xN" but for y-axis and z-axis yoN, zoN: same as "xoN" but for y-axis and z-axis LN: sets left side of the object as the self-rotation center if N specified, offsets the center by N toward center of the object. RN, TN, BN: same for right, top, bottom ZN: offsets the center of the self-rotation by N toward z-axis (front/rear) ex. name="10 x20" -> rotates 20 degree around x-axis, rotation origin is the center of the object itself. then offsets the center of this object's z-axis value by 10. *If there's a path named "origin" in the selection, this script sets its center as the rotation center. OTHER NOTE: *If "fix z-order" option is checked, this script changes the z-order of the objects according to the z-axis value of the center of the each object. It doesn't always work properly, and this process may destruct the groups. *This script removes the transformed objects that had exceeded the value which multiplied the virtual sphere radius by predefined value. with a message dialog. The default predefined value is 2.0. Edit "EXCESSIVE_TRANSFORM_THRESHOLD" value below to change it. */ const EXCESSIVE_TRANSFORM_THRESHOLD = 2.0; // set max( this value, max(selection.width, selection.height)) to virtual sphere's radius const MIN_RADIUS = 100; // ---------------------------------------------- var Vector3d = function(x,y,z){ this.x = x; this.y = y; this.z = z; } Vector3d.prototype = { clone : function(){ return new Vector3d(this.x, this.y, this.z); }, add : function(v){ this.x += v.x; this.y += v.y; this.z += v.z; return this; }, sub : function(v){ this.x -= v.x; this.y -= v.y; this.z -= v.z; return this; }, xrot : function(t){ var c = Math.cos(t); var s = Math.sin(t); var y2 = this.y*c - this.z*s; var z2 = this.y*s + this.z*c; this.y = y2; this.z = z2; return this; }, yrot : function(t){ var c = Math.cos(t); var s = Math.sin(t); var x2 = this.x*c + this.z*s; var z2 = - this.x*s + this.z*c; this.x = x2; this.z = z2; return this; }, zrot : function(t){ var c = Math.cos(t); var s = Math.sin(t); var x2 = this.x*c - this.y*s; var y2 = this.x*s + this.y*c; this.x = x2; this.y = y2; return this; }, rot : function(rangle){ // RotationAngle rangle this.xrot(rangle.xr); this.yrot(rangle.yr); this.zrot(rangle.zr); return this; }, to2d : function(f){ this.x = f * this.x / (f - this.z); this.y = f * this.y /(f - this.z); return this; } } // ---------------------------------------------- var Vector3dSegment = function(point, handleIn, handleOut){ this.point = point; this.handleIn = handleIn; this.handleOut = handleOut; } // ---------------------------------------------- var RotationAngle = function(xr, yr, zr){ this.xr = xr; this.yr = yr; this.zr = zr; } RotationAngle.prototype = { reset : function(){ this.xr = 0; this.yr = 0; this.zr = 0; return this; }, nonZero : function(){ return this.xr != 0 || this.yr != 0 || this.zr != 0; } } // ---------------------------------------------- var g = { sel : [], // selected paths sr : null, // rectangle around the selection so : null, // center of the selection inv_radius : null, vpaths : [], // array of Vector3dSegments centers : [], // center of the each object. used to fix z order if fix_z_order==true last_rot : null, // last rotation config last_pnt : null, // previous event.point that processed updated : false, // redraw when mouseUp if true p0 : new Point(0,0), v0 : new Vector3d(0,0,0) }; // ================================================================== var values = { focal_length: 400, global_depth: 0, fix_z_order: true }; var components = { focal_length: { label: 'focal length' }, global_depth: { label: 'global depth' }, fix_z_order: { label: 'fix z-order', type: 'boolean' } }; var palette = new Palette('rot3d', components, values); // ---------------------------------------------- function onMouseDown(event){ with( document.activeLayer ){ if( locked || hidden ){ Dialog.alert("please unlock and show the active layer"); return; } } // get selected paths g.sel = document.getItems({ type:Path, selected: true}); g.sr = getItemSetRect(g.sel); if(g.sr == null) return; if(g.so == null) g.so = g.sr.center; g.inv_radius = 1 / (Math.max(Math.max(g.sr.width, g.sr.height) / 2, MIN_RADIUS)); var opts = { z:0, r:new RotationAngle(0,0,0), or:new RotationAngle(0,0,0), center:null}; var vectHeight = g.v0.clone(); vectHeight.z = - values.global_depth; for(var i=0; i < g.sel.length; i++){ opts.or.reset(); var vsegs = []; // array of Vector3dSegment for(var j = 0; j < g.sel[i].segments.length; j++){ var seg = g.sel[i].segments[j]; vsegs.push( new Vector3dSegment( new Vector3d(seg.point.x - g.so.x, seg.point.y - g.so.y, 0), new Vector3d(seg.handleIn.x, seg.handleIn.y, 0), new Vector3d(seg.handleOut.x, seg.handleOut.y, 0))); } // push the coord for center at the last of the array vsegs.push( new Vector3dSegment( new Vector3d(g.sel[i].bounds.center.x - g.so.x, g.sel[i].bounds.center.y - g.so.y, 0), g.v0.clone(), g.v0.clone())) getSelfRotationFromName(g.sel[i], vsegs, opts); getRotationFromName(g.sel[i], opts); if(opts.or.nonZero()){ rotSegDat(vsegs, opts.or); } for(var j=0; j < vsegs.length; j++){ vsegs[j].point.add( vectHeight ); } g.centers.push( vsegs.pop().point ); // center g.vpaths.push(vsegs); } g.last_pnt = event.point; g.updated = false; } // ---------------------------------------------- function getSelfRotationFromName(obj, vsegs, opts){ if( obj instanceof Layer ) return; var sname = obj.name || ""; if( sname != "" ){ opts.r.reset(); opts.center = obj.bounds.center; oz = 0; // xN: x-axis rotation. uses center of the object itself as origin if(sname.match(/x(-?\d+)/)){ opts.r.xr += toRad(RegExp.$1); } if(sname.match(/y(-?\d+)/)){ opts.r.yr += toRad(RegExp.$1); } if(sname.match(/z(-?\d+)/)){ opts.r.zr += toRad(RegExp.$1); } // LR & offset if(sname.match(/([RL])/)){ if(RegExp.$1 == "R"){ opts.center.x = obj.bounds.right; if(sname.match(/R(\d+)/)){ opts.center.x -= parseFloat(RegExp.$1); } } else { // L opts.center.x = obj.bounds.left; if(sname.match(/L(\d+)/)){ opts.center.x += parseFloat(RegExp.$1); } } } // TB & offset if(sname.match(/([TB])/)){ if(RegExp.$1 == "T"){ opts.center.y = obj.bounds.top; if(sname.match(/T(\d+)/)){ opts.center.y -= parseFloat(RegExp.$1); } } else { // B opts.center.y = obj.bounds.bottom; if(sname.match(/B(\d+)/)){ opts.center.y += parseFloat(RegExp.$1); } } } // Z & offset if(sname.match(/Z(-?\d+)/)){ oz = parseFloat(RegExp.$1); } // actual rotation if(opts.r.nonZero()){ rotSegDat2(vsegs, opts.r, opts.center - g.so, oz); } // option by the name of the object. N means a number // N: height ( - global depth) (must be at the beginning of its name) if( sname.match(/^([\d\.-]+)/) && !isNaN(RegExp.$1) ){ var vectHeight = parseFloat(RegExp.$1); for(var j = 0; j < vsegs.length; j++){ vsegs[j].point.z += vectHeight; } } } if( obj.parent instanceof Item ){ getSelfRotationFromName(obj.parent, vsegs, opts); } } // ---------------------------------------------- function getRotationFromName(obj, opts){ if( obj instanceof Layer) return; var sname = obj.name || ""; if( sname != "" ){ // xoN: x-axis rotation. uses global origin (default: center of the selection) if(sname.match(/xo(-?\d+)/)){ opts.or.xr += toRad(RegExp.$1); } if(sname.match(/yo(-?\d+)/)){ opts.or.yr += toRad(RegExp.$1); } if(sname.match(/zo(-?\d+)/)){ opts.or.zr += toRad(RegExp.$1); } } if( obj.parent instanceof Item ){ getRotationFromName(obj.parent, opts); } } // ---------------------------------------------- function toRad(deg){ return ( parseFloat(deg) - 0 ) * 0.0174532925199433; // * (PI / 180); } // ---------------------------------------------- function onMouseDrag(event){ if(g.last_pnt == null || g.last_pnt.getDistance(event.point) < 1) return; g.last_pnt = event.point; if(g.sel.length < 1 || g.sr == null) return; var v = (g.last_pnt - g.so) * g.inv_radius; if(v.getDistance(g.p0) > 0.999) v = v.normalize() * 0.999; // rotation angles var t = Math.asin(v.y); if(isNaN(t)) return; if(g.last_rot == null) g.last_rot = new RotationAngle(0,0,0); g.last_rot.xr = -t; g.last_rot.yr = Math.asin(v.x / Math.cos(t)); if(isNaN( g.last_rot.yr )) return; g.last_rot.zr = 0; for(var i = 0; i< g.sel.length; i++){ var seg = g.sel[i].segments; for(var j = 0; j < seg.length; j++){ var vp = g.vpaths[i][j].point; var pnt = view3d(g.vpaths[i][j].point, g.v0, g.p0); seg[j].point = pnt + g.so; seg[j].handleIn = view3d(g.vpaths[i][j].handleIn, vp, pnt); seg[j].handleOut = view3d(g.vpaths[i][j].handleOut, vp, pnt); } } g.updated = true; } // ---------------------------------------------- function view3d(vt, v2, p){ var vect = vt.clone().add(v2); vect.rot(g.last_rot); vect.to2d(values.focal_length); return new Point(vect.x - p.x, vect.y - p.y); } // ---------------------------------------------- function rotSegDat(vsegs, rangle){ for(var i = 0; i < vsegs.length; i++){ vsegs[i].handleIn.add(vsegs[i].point); vsegs[i].handleOut.add(vsegs[i].point); vsegs[i].point.rot(rangle); vsegs[i].handleIn.rot(rangle).sub(vsegs[i].point); vsegs[i].handleOut.rot(rangle).sub(vsegs[i].point); } } // ---------------------------------------------- function rotSegDat2(vsegs, rangle, o, oz){ var vo = new Vector3d(o.x, o.y, oz); for(var i = 0; i < vsegs.length; i++){ vsegs[i].point.sub(vo); vsegs[i].handleIn.add(vsegs[i].point); vsegs[i].handleOut.add(vsegs[i].point); vsegs[i].point.rot(rangle).add(vo); vsegs[i].handleIn.rot(rangle).add(vo).sub(vsegs[i].point); vsegs[i].handleOut.rot(rangle).add(vo).sub(vsegs[i].point); } } // ---------------------------------------------- function onMouseUp(){ var errmsg = ""; if(g.sel.length > 0 && g.updated){ var max_size = Math.max(g.sr.width, g.sr.height) * EXCESSIVE_TRANSFORM_THRESHOLD; var i, j; for(i = g.sel.length - 1; i >= 0; i--){ if(Math.max(g.sel[i].bounds.height, g.sel[i].bounds.width) > max_size){ g.sel[i].remove(); g.sel.remove(i); g.centers.remove(i); errmsg = "some objects were removed due to excessive transform\n"; } } // fix z-order if(values.fix_z_order && g.last_rot != null && g.sel.length > 0){ for(i = 0; i < g.centers.length; i++){ g.centers[i].rot(g.last_rot); } var idx; for(i = 0; i < g.sel.length; i++){ idx = -1; for(j = 0; j < g.sel.length; j++){ if(i == j) continue; if(g.centers[i].z > g.centers[j].z){ if(idx < 0 || g.sel[idx].isBelow(g.sel[j])){ idx = j; } } } if(idx >= 0 && g.sel[i].isBelow(g.sel[idx])){ g.sel[i].moveAbove(g.sel[idx]); } } } } resetValues(); if(errmsg != ""){ Dialog.alert(errmsg); } } // ---------------------------------------------- function resetValues(){ //g.sel = null; g.vpaths = []; g.centers = []; g.so = null; g.sr = null; g.last_pnt = null; g.last_rot = null; g.updated = false; } // ---------------------------------------------- function getItemSetRect(set){ if(set.length < 1) return null; var tmp_rect = null; for(var i = set.length - 1; i >= 0; i--){ // if there's a path named "origin" in the selection, // set its center as the rotation center if(g.so == null && set[i].name == "origin"){ g.so = set[i].bounds.center; if(set.length > 1){ set.remove(i); continue; } } with(set[i].bounds){ if(tmp_rect == null){ tmp_rect = [top, right, bottom, left]; } else { if(top > tmp_rect[0]) tmp_rect[0] = top; if(right > tmp_rect[1]) tmp_rect[1] = right; if(bottom < tmp_rect[2]) tmp_rect[2] = bottom; if(left < tmp_rect[3]) tmp_rect[3] = left; } } } return new Rectangle(new Point(tmp_rect[3], tmp_rect[0]), new Point(tmp_rect[1], tmp_rect[2])); }