/* File: ..//src//photoshop//secondLifeBoxPrim.jsx Date: Sat Dec 27 15:17:32 MYT 2008 Author: David Van Brink This script is part of the omino adobe script suite. The latest version can be found at http://omino.com/pixelblog/. I write these because I like to. Please enjoy as you see fit. Questions to poly@omino.com, subject line should start with "plugins" so my spam filter lets it in. This file has been preprocessed to be standalone. I develop them against some reusable libraries -- such as for dialog layout -- but for distribution it's nicer to have just one file. dvb 2007. */ /* Description: This Photoshop script creates templates for the faces of a box in Second Life. Settings are entered through a simple dialog. It handles holes, taper, and skew, but not twist. Very useful for creating tuned textures. :: */ var D_MARGIN = 4; var D_CONTROLHEIGHT = 20; var D_BUTTONWIDTH = 100; var D_CONTROLLABELWIDTH = 150; var D_CONTROLWIDTH = 150; var D_DIALOG_WIDTH = 400; var S2 = 1.41421356237309504880; // add a control (of fixed height) to the dialog // optional params: itemType (default: edittext) function appendControl(dialog,itemLabel,itemName,defaultValue,itemType,radioChoices) { if(!itemType) itemType = 'edittext'; var y = dialog.curYPos; var itemHeight = D_CONTROLHEIGHT; var itemBump = itemHeight + D_MARGIN; var result; if(itemLabel != "") itemLabel += ":"; var label = dialog.add('statictext',[20,y,20 + D_CONTROLLABELWIDTH,y+itemHeight],itemLabel); var controlBox = new Object(); controlBox.left = 20 + D_CONTROLLABELWIDTH + 10; controlBox.top = y; controlBox.right = controlBox.left + D_CONTROLWIDTH; controlBox.bottom = controlBox.top + itemHeight; label.justify = "right"; if(itemType == 'radiobutton') { result = new Object(); // ersatz control, just so we can put the value in it result.value = defaultValue; var i; for(i = 0; i < radioChoices.length; i++) { var choice = radioChoices[i]; if(i > 0) { controlBox.top += itemHeight + D_MARGIN; controlBox.bottom += itemHeight + D_MARGIN; } // each radiobutton object pokes its choice into the ersatz control, // so it looks like a simple value. var rb = dialog.add('radiobutton',controlBox,choice); rb.value = choice == defaultValue; rb.theChoice = choice; rb.theGroupErsatzControl = result; rb.onClick = function(){this.theGroupErsatzControl.value = this.theChoice;}; } } else if(itemType == 'edittext') { result = dialog.add(itemType,controlBox,defaultValue); result.onChange = function(){this.value = this.text;}; // make them all .value accessible } else if(itemType == 'checkbox') { var result = dialog.add(itemType,controlBox,radioChoices); // just checbox label in this case result.value = defaultValue; } dialog.curYPos = controlBox.bottom + D_MARGIN; dialog[itemName] = result; return result; } function appendBoxedText(dialog,text,width,height,doBox) { if(doBox == undefined) doBox = true; if(!width) width = 500; if(!height) height = 500; var container = dialog; var b2 = new Object(); if(doBox) { var b = new Object(); b.top = dialog.curYPos; b.bottom = b.top + height + 2 * D_MARGIN; b.left = D_MARGIN; b.right = b.left + width + 2 * D_MARGIN; dialog.curYPos = b.bottom + D_MARGIN; container = dialog.add('panel',b); b2.left = D_MARGIN; b2.top = D_MARGIN; b2.right = b2.left + width; b2.bottom = b2.top + height; } else { b2.left = D_MARGIN; b2.top = dialog.curYPos; b2.right = b2.left + width; b2.bottom = b2.top + height; dialog.curYPos += height + D_MARGIN; } container.add('statictext',b2,text,{multiline:true}); } function appendSeparator(dialog,height,barWidth) { if(barWidth) { var b = new Object(); b.top = dialog.curYPos + height / 2; b.bottom = b.top; b.left = D_MARGIN; b.right = b.left + barWidth; var barHeight = 2; b.top -= barHeight / 2; b.bottom = b.top + barHeight; dialog.add('panel',b); } dialog.curYPos += height; } function appendOKCancel(dialog) { var y = dialog.curYPos; var cancelRect = new Object(); var okRect = new Object(); cancelRect.left = D_MARGIN cancelRect.top = y; cancelRect.right = cancelRect.left + D_BUTTONWIDTH; cancelRect.bottom = cancelRect.top + D_CONTROLHEIGHT; okRect.left = cancelRect.right + D_MARGIN + D_MARGIN; okRect.top = y; okRect.right = okRect.left + D_BUTTONWIDTH; okRect.bottom = okRect.top + D_CONTROLHEIGHT; var cancelBtn = dialog.add('button',cancelRect,'Cancel',{name:'cancel'}); var okBtn = dialog.add('button',okRect,'OK',{name:'ok'}); cancelBtn.theDialog = dialog; cancelBtn.onClick = function(){this.theDialog.close(0);}; // 0 on cancel okBtn.theDialog = dialog; okBtn.onClick = function(){this.theDialog.close(1);}; // 1 on ok dialog.curYPos = okRect.bottom + D_MARGIN; } function trimDialogBounds(dialog) { var xMax = 20; var yMax = 20; var n = dialog.children.length; for(i = 0; i < n; i++) { var aChild= dialog.children[i]; var aChildBounds = aChild.bounds; if(aChildBounds.right > xMax) xMax = aChildBounds.right; if(aChildBounds.bottom > yMax) yMax = aChildBounds.bottom; } dialog.bounds.right = dialog.bounds.left + xMax + D_MARGIN; dialog.bounds.bottom = dialog.bounds.top + yMax + D_MARGIN; } function buildDialog() { var dlg = new Window('dialog',"Box Prim Texture Helper",[100,100,500,500]); dlg.curYPos = 20; var groupGap = 12; appendBoxedText(dlg, "This Photoshop script creates templates for a Second Life Box primitive. " + "This can be handy for creating specialized textures. " + "To use, enter the parameters of your Box primitive as seen from the Second Life editor. ", D_DIALOG_WIDTH,120); appendSeparator(dlg,10,D_DIALOG_WIDTH); appendControl(dlg,"Name","primName","unnamed"); appendSeparator(dlg,10,D_DIALOG_WIDTH); appendBoxedText(dlg,"Prim Parameters:",D_DIALOG_WIDTH,20,false); appendControl(dlg,"Size X (0 to 10m)","xSize",1); appendControl(dlg,"Y (0 to 10m)","ySize",1); appendControl(dlg,"Z (0 to 10m)","zSize",1); appendSeparator(dlg,groupGap); appendControl(dlg,"Cut Begin (0.0 to 1.0)","cutBegin",0); appendControl(dlg,"End (0.0 to 1.0)","cutEnd",1.0); appendSeparator(dlg,groupGap); appendControl(dlg,"Hollow (0 to 100)","holeSize",50); appendControl(dlg,"Hollow Shape","holeShape","square",'radiobutton',["square","circle","triangle"]); appendSeparator(dlg,groupGap); appendControl(dlg,"Top Size X (0.0 to 1.0)","xTopSize",1); appendControl(dlg,"Y (0.0 to 1.0)","yTopSize",1); appendSeparator(dlg,groupGap); appendControl(dlg,"Top Shear X (-0.5 to 0.5)","xTopShear",0); appendControl(dlg,"Y (-0.5 to 0.5)","yTopShear",0); appendControl(dlg,"Faces","makeTopOnly",true,'checkbox',"Top Only"); appendSeparator(dlg,10,D_DIALOG_WIDTH); appendBoxedText(dlg,"Document Settings:",D_DIALOG_WIDTH,20,false); appendControl(dlg,"Final Texture Size","finalTextureSize",512); appendControl(dlg,"Maximum Document Size","maxDocumentSize",1024); appendSeparator(dlg,10,D_DIALOG_WIDTH); appendOKCancel(dlg); appendSeparator(dlg,10); trimDialogBounds(dlg); return dlg; } // Get array of 5 points suitable for use in photoshop selection rectangles. function getRectSelectionPoints(left,top,right,bottom) { var p = Array( Array(left,top), Array(right,top), Array(right,bottom), Array(left,bottom), Array(left,top)); return p; } // Return a fresh photoshop document of requested size function buildDoc(x,y,docName) { if(!docName) docName = "box prim guide"; var docX = x; var docY = y; s// lets talk pixels... 1024 inches makes trouble. app.preferences.rulerUnits = Units.PIXELS; var aDoc = app.documents.add(docX, docY, 72, docName); app.activeDocument = aDoc; aDoc.docX = docX; aDoc.docY = docY; // also... select all aDoc.selection.selectAll(); return aDoc; } // // Second Life uses a "cut" value to indicate a radial // proportion. A cut goes from 0 to 1, where 1 is the full // loop. For a circular hole (or cylindrical prim) the cut // is proportional to an angle. For other shapes, it is // a proportion of the perimeter walk. // This function gives the circular/angular version. // function polar2xy(cut) // returns -1,1 ranges { // angle for prim work is a "cut" ranging from 0..1, forming a circle. // also, 0 (and 1) is at (-1,-1). So. // (but we invert Y since pixels go downward... works out) var result = new Object(); var theta = (cut - 3.0 / 8.0) * 2 * 3.1415926535897932; result.x = Math.cos(theta); result.y = -Math.sin(theta); return result; } // ABOUT cuts in SL Prims -- the logic is this. // The cut ranges from 0.0 to 1.0, and goes counterclockwise // from lower left (-1,-1) up and around. // The line of the cut has an angle APPROXIMATELY proportional, // but not exactly. The cut begins at the hole (or center if // hollow == 0) and proceeds to the edge. The placement of // the endpoints is along the perimeter of the internal hole // and the external surface. So a box prim, 0 to 0.25 is linear // along the bottom edge. If the hole is round, then the cut // line starts at r * cut * 2pi. If the hole is square or // triangular, then it's divided in 4 or 3 segments of the unit // cut range... // TODO -- calculate cut begin and end correctly. The way I do it // now assumes radial cuts. // cuts work funny in SL box prims: // It ranges the circle from 0..1, starting at (-1,-1) (for a 2unit prim), // and going counterclockwise. Furthermore, the cut specifies the PORTION of // the face intersected, not an angle. // // number of sides 1 means cylinder/circlehole, // number of sides 3 or 4 for square or triangle outside or inside. // // Note: this function does NOT account for the fact that // square holes are bigger by sqrt(2). // function cut2xy(cut,sides,radius) { var result = new Object(); if(sides == 1) // cylinder? plain old polar { result = polar2xy(cut); result.x *= radius; result.y *= radius; return result; } // real work. var edgeNumber = cut * sides; var edgePortion = edgeNumber; edgeNumber = Math.floor(edgeNumber); edgePortion = edgePortion - edgeNumber; // from 0 to 1 var p1 = polar2xy(edgeNumber / sides); var p2 = polar2xy((edgeNumber + 1) / sides); result.x = mapPortion(p1.x,p2.x,edgePortion) * radius; result.y = mapPortion(p1.y,p2.y,edgePortion) * radius; return result; } // portion of the outside of a box prim for a cut... function cut2portion(cut) { cut = cut * 4; var result = cut - Math.floor(cut); return result; } // Given a hole size and number of sides, return a // pleasant array of selection points for the hole. // Scale to appropriate document size. // flipY is a boolean for making the underside of a prim function getDocHoleSelection(aDoc,holeSize,holeSides,flipY) { var pointCount = holeSides; var radius = holeSize; if(holeSides == 1) pointCount = 256; // circular hole else if(holeSides == 4) radius *= S2; // square hole // do a bunch of points... var i; var selectionPoints = new Array(); for(i = 0; i <= pointCount; i++) // first and last points overlap { var xy = cut2xy(i / pointCount,holeSides,radius); if(flipY) xy.y = -xy.y; var ptX = mapRange(-1,1,0,aDoc.docX,xy.x); // (aDoc.docX + aDoc.docX * xy.x * holeSize) / 2; var ptY = mapRange(-1,1,0,aDoc.docY,xy.y); // (aDoc.docY + aDoc.docY * xy.y * holeSize) / 2; selectionPoints[i] = new Array(ptX,ptY); } return selectionPoints; } function cutHoleSelection(aDoc,holeSize,holeSides,flipY) { if(holeSize == 0) return; var selectionPoints; selectionPoints = getDocHoleSelection(aDoc,holeSize,holeSides,flipY); aDoc.selection.select(selectionPoints,SelectionType.DIMINISH); } // Assuming that whole rect is selected and the // appropriate shape inner hole has been removed, // no remove a wedge representing the cut-range. function sectCutSelection(aDoc,holeSize,holeSides,cutBegin,cutEnd,flipY) { // no cut? no problem. if(cutBegin <= 0 && cutEnd >= 1) return; if(holeSides == 4) // for square hole, we enlarge to make concentric holeSize *= S2; // sect out a big pie based on the cut wedge var selectionPoints = new Array(); // from center, to position on hole, to position // on outside, then to a position safely past // the outside for an orbit around the shape, // then back in, to the center. // // start with unit-ized points, then rescale them // to the rectangle. var index = 0; var o = new Object(); o.x = 0; o.y = 0; var innerCutBeginXY = cut2xy(cutBegin, holeSides, holeSize); var outerCutBeginXY = cut2xy(cutBegin, 4, S2); var outerCutEndXY = cut2xy(cutEnd, 4, S2); var innerCutEndXY = cut2xy(cutEnd, holeSides, holeSize); selectionPoints[index++] = o; selectionPoints[index++] = innerCutBeginXY; selectionPoints[index++] = outerCutBeginXY; // march around the outside... var going = 1; var cut = cutBegin; while(going) { if(cut >= cutEnd) { cut = cutEnd; going = 0; } var xy = cut2xy(cut,4,6); // safe distant radius selectionPoints[index++] = xy; cut += 0.1; } selectionPoints[index++] = outerCutEndXY; selectionPoints[index++] = innerCutEndXY; selectionPoints[index++] = o; // lastly, scale every point to appropriate range // careful! some of the array indices point to the same xy objects! var count = index; for(index = 0; index < count; index++) { var xy = selectionPoints[index]; var pixelX = mapRange(-1,1,0,aDoc.docX,xy.x); var y = xy.y; if(flipY) y = -y; var pixelY = mapRange(-1,1,0,aDoc.docY,y); selectionPoints[index] = new Array(pixelX,pixelY); // must be array type now } aDoc.selection.select(selectionPoints,SelectionType.INTERSECT); return; } // hy pot e noose function hyp(a,b) { return Math.sqrt(a * a + b * b); } // return dimensions, based on prim width but then shearing, too // obviously height doesnt matter here function getPrimTop(width,topSize,rightShear) { var x = new Object(); x.bottomLeft = 0; x.bottomRight = width; x.bottomWidth = width; x.topLeft = (rightShear + (1 - topSize) / 2) * width; x.topRight = (1.0 + rightShear - (1.0 - topSize) / 2.0) * width; x.topWidth = x.topRight - x.topLeft; x.topLeftCut = x.topLeft; x.topRightCut = x.topRight; x.bottomLeftCut = x.bottomLeft; x.bottomRightCut = x.bottomRight; x.rightMost = x.bottomRight; if(x.topRight > x.rightMost) x.rightMost = x.topRight; x.leftMost = x.bottomLeft; if(x.topLeft < x.leftMost) x.leftMost = x.topLeft; return x; } // map between two known ranges function mapRange(srcLow,srcHigh,dstLow,dstHigh,v) { v -= srcLow; v = v * (dstHigh - dstLow) / (srcHigh - srcLow); v += dstLow; return v; } // map from 0..1 to a range function mapPortion(low,high,portion) { return mapRange(0,1,low,high,portion); } // We create a "face" for each side of the prim // these parameters are then given with respect to the face of interest. // We keep Z where possible, and Y up otherwise. function newFace(width,height,depth,inSize,inShear,topSize,rightShear,thisFaceCutBegin,thisFaceCutEnd,cutBegin,cutEnd,faceName,description,primParams) { var face = new Object(); face.width = width; face.height = height; face.depth = depth; face.topSize = topSize; face.rightShear = rightShear; var inPrimTop = getPrimTop(depth,inSize,inShear); // the effective height is greater if the face isnt verticle, into the page.... face.surfaceHeight = hyp(face.height,inPrimTop.topLeft); // the required width is greater if the shear brings it off the original rectangle at all... face.primTop = getPrimTop(face.width,face.topSize,face.rightShear); face.surfaceWidth = face.primTop.rightMost - face.primTop.leftMost; // and trim the primTop struct, if we're mid-cut if(cutBegin > thisFaceCutBegin) { var portion = cut2portion(cutBegin); face.primTop.topLeftCut = mapPortion(face.primTop.topLeft,face.primTop.topRight,portion); face.primTop.bottomLeftCut = mapPortion(face.primTop.bottomLeft,face.primTop.bottomRight,portion); } if(cutEnd < thisFaceCutEnd) { var portion = cut2portion(cutEnd); face.primTop.topRightCut = mapPortion(face.primTop.topLeft,face.primTop.topRight,portion); face.primTop.bottomRightCut = mapPortion(face.primTop.bottomLeft,face.primTop.bottomRight,portion); } face.faceName = faceName; face.description = description; face.primParams = primParams; return face; } // looking at all the faces, we choose a suitable pixels/meter so that they're // all the same, the max doc size isnt exceeded, and its at least the texture size. // in general, the smallest feature should be mapped onto the final // texture size, unless that it would exceed our max size. then reduce. // function setEachFaceDocDimensions(finalTextureSize,maxDocumentSize,faces) { var n = faces.length; var i; // walk through them all to find consistent pixels/meter var overallPixelsPerMeter = 100000000; for(i = 0; i < n; i ++) { var face = faces[i]; var bottomWidth = face.primTop.bottomWidth; var topWidth = face.primTop.topWidth; var surfaceHeight = face.surfaceHeight; var surfaceWidth = face.surfaceWidth; var pixelsNeeded = 0; var pixelsPerMeter = 0; // try to assign texture size to the bottom-edge pixelsNeeded = finalTextureSize; pixelsPerMeter = finalTextureSize / bottomWidth; // does shear cause us to exceed? if(surfaceWidth * pixelsPerMeter > maxDocumentSize) { pixelsNeeded = maxDocumentSize; pixelsPerMeter = maxDocumentSize / surfaceWidth; } // does height cause us to exceed? if(surfaceHeight * pixelsPerMeter > maxDocumentSize) { pixelsNeeded = maxDocumentSize; pixelsPerMeter = maxDocumentSize / surfaceHeight; } if(pixelsPerMeter < overallPixelsPerMeter) overallPixelsPerMeter = pixelsPerMeter; } // now we know the pixels per meter... // poke in document sizes for all! for(i = 0; i < n; i++) { var face = faces[i]; face.docPixelsX = Math.round(face.surfaceWidth * overallPixelsPerMeter); face.docPixelsY = Math.round(face.surfaceHeight * overallPixelsPerMeter); if(face.docPixelsX && face.docPixelsY) { var aDoc = buildDoc(face.docPixelsX,face.docPixelsY,face.faceName); face.doc = aDoc; var defaultLayer = face.doc.activeLayer; doFaceTextNote(face); face.doc.activeLayer = defaultLayer; app.foregroundColor = getColor(80,80,80); face.doc.selection.fill(app.foregroundColor); doFaceSelection(face); if(face.isBottom) { cutHoleSelection(aDoc,face.holeSize,face.holeSides,1); sectCutSelection(aDoc,face.holeSize,face.holeSides,face.cutBegin,face.cutEnd,1); } else if(face.isTop) { cutHoleSelection(aDoc,face.holeSize,face.holeSides); sectCutSelection(aDoc,face.holeSize,face.holeSides,face.cutBegin,face.cutEnd); } app.foregroundColor = getColor(255,255,255); face.doc.selection.fill(app.foregroundColor); app.foregroundColor = getColor(0,0,0); } } } function getFacePixelX(face,metersX) { var pixelX = face.docPixelsX * (metersX - face.primTop.leftMost) / (face.primTop.rightMost - face.primTop.leftMost); return pixelX; } // select the rectangle for a face, taking into account if it is // lying on a cut vertical part function doFaceSelection(face) { var selectionPoints = new Array(); selectionPoints[0] = [getFacePixelX(face,face.primTop.topLeftCut),0]; selectionPoints[1] = [getFacePixelX(face,face.primTop.topRightCut),0]; selectionPoints[2] = [getFacePixelX(face,face.primTop.bottomRightCut),face.docPixelsY]; selectionPoints[3] = [getFacePixelX(face,face.primTop.bottomLeftCut),face.docPixelsY]; selectionPoints[4] = [getFacePixelX(face,face.primTop.topLeftCut),0]; face.doc.selection.select(selectionPoints); //,SelectionType.DIMINISH); } function getColor(r,g,b) // 0..255 each { var rgbColor = new RGBColor(); rgbColor.red = r; rgbColor.green = g; rgbColor.blue = b; var fc = new SolidColor(); fc.rgb = rgbColor; return fc; } function doFaceTextNote(face) { var fc = getColor(0,0,0); var newLayerRef = face.doc.artLayers.add(); newLayerRef.kind = LayerKind.TEXT; var pos = new Array(6,20); var fSize = 12; newLayerRef.textItem.contents = face.faceName + "\r" + face.description + "\r" + face.primParams; newLayerRef.textItem.color = fc; newLayerRef.textItem.size = fSize; newLayerRef.textItem.position = pos; } function main() { var a = buildDialog(); var result = a.show(); if(result == 0) return; // cancel var xSize = a.xSize.text * 1.0; var ySize = a.ySize.text * 1.0; var zSize = a.zSize.text * 1.0; var holeSize = a.holeSize.text * 1.0; var holeShape = a.holeShape.value; var cutBegin = a.cutBegin.text * 1.0; var cutEnd = a.cutEnd.text * 1.0; var xTopSize = a.xTopSize.text * 1.0; var yTopSize = a.yTopSize.text * 1.0; var xTopShear = a.xTopShear.text * 1.0; var yTopShear = a.yTopShear.text * 1.0; var makeTopOnly = a.makeTopOnly.value; // if true, just the top var holeSides = 1; if(holeShape == "triangle") holeSides = 3; else if(holeShape == "square") holeSides = 4; var primName = a.primName.text; var s = "Size: (" + xSize + "," + ySize + "," + zSize + ")" + "\rHollow: " + holeSize + " " + holeShape + "\rCut: (" + cutBegin + "," + cutEnd + ")" + "\rTop Size: (" + xTopSize + "," + yTopSize + ")" + "\rTop Shear: (" + xTopShear + "," + yTopShear + ")"; holeSize = holeSize / 100.0; // there. var finalTextureSize = a.finalTextureSize.text * 1.0; var maxDocumentSize = a.maxDocumentSize.text * 1.0; var faces = new Array(); if(!makeTopOnly) { if(cutBegin < .25 && cutEnd > 0) { var faceYNeg = newFace(xSize,zSize,ySize,yTopSize,yTopShear,xTopSize,xTopShear, 0.00 , 0.25 ,cutBegin,cutEnd, primName + "_front","Lesser Y: Z-up X-right",s); faces[faces.length] = faceYNeg; } if(cutBegin < .50 && cutEnd > .25) { var faceXPos = newFace(ySize,zSize,xSize,xTopSize,-xTopShear,yTopSize,yTopShear, 0.25 , 0.50 ,cutBegin,cutEnd, primName + "_right","Greater X: Z-up, Y-right",s); faces[faces.length] = faceXPos; } if(cutBegin < .75 && cutEnd > .50) { var faceYPos = newFace(xSize,zSize,ySize,yTopSize,-yTopShear,xTopSize,-xTopShear, 0.50 , 0.75 ,cutBegin,cutEnd, primName + "_back","Greater Y: Z-up, X-left",s); faces[faces.length] = faceYPos; } if(cutBegin < 1.00 && cutEnd > 0.75) { var faceXNeg = newFace(ySize,zSize,xSize,xTopSize,xTopShear,yTopSize,-yTopShear, 0.75 , 1.00 ,cutBegin,cutEnd, primName + "_left","Lesser X: Z-up, Y-left",s); faces[faces.length] = faceXNeg; } var faceZNeg = newFace(xSize,ySize,zSize,1,0,1,0,0,0,0,1, primName + "_bottom","Lesser Z: Y-down, X-right",s); faces[faces.length] = faceZNeg; faceZNeg.cutBegin = cutBegin; faceZNeg.cutEnd = cutEnd; faceZNeg.holeShape = holeShape; faceZNeg.holeSides = holeSides faceZNeg.holeSize = holeSize; faceZNeg.isBottom = true; } // makeTopOnly if(xTopSize && yTopSize) { var faceZPos = newFace(xSize * xTopSize,ySize * yTopSize,zSize,1,0,1,0,0,0,0,1, primName + "_top","Greater Z: Y-up, X-right",s); faces[faces.length] = faceZPos; faceZPos.cutBegin = cutBegin; faceZPos.cutEnd = cutEnd; faceZPos.holeShape = holeShape; faceZPos.holeSides = holeSides faceZPos.holeSize = holeSize; faceZPos.isTop = true; } setEachFaceDocDimensions(finalTextureSize,maxDocumentSize,faces); //var aDoc = buildDoc(xSize,ySize); //var newLayerRef = aDoc.artLayers.add(); // default type normal? //cutHoleSelection(aDoc,holeSize,holeShape); //sectCutSelection(aDoc,cutBegin,cutEnd); } main(); // end of file