-- ArchiFrame WUP cnc-writer -- See www.lua.org for Lua manual. -- Extension functions - see manual --------------------------------------------------------------------------- -- CODE SHARED WITH ARCHIFRAME START --> EPS=0.0001 PI=3.141592653589793 PI2=PI/2.0 PI180=PI/180.0 PI1800=PI/1800.0 -- # Frame machinings types EMcFrAngledBegOld = 100 EMcFrAngledBeg = 101 EMcFrAngledBegTenon = 110 -- Also dovetail EMcFrBegHiddenShoe = 111 EMcFrAngledEndOld = 200 EMcFrAngledEnd = 201 EMcFrAngledEndTenon = 210 EMcFrEndHiddenShoe = 211 EMcFrOpening = 300 -- Opening - unused now EMcFrGroove = 301 -- Logsin tapainen vapaa ura EMcFrDrill = 302 -- Drilling EMcFrMarking = 303 -- Marking EMcFrReinforce = 304 EMcFrSaw = 305 EMcFrNailGroup = 306 EMcFrNailLine = 307 EMcFrTenonSide = 400 -- Also dovetail EMcFrBalkJoint = 401 -- Narrowed balk --------------------------------------------------------------------------- -- ARCHIFRAME & ARCHILOGS COMMON FUNCTIONS function toLog(str) ac_environment("tolog", str) end -- Dumps given variable into string function DumpTblInt(o) local s,k,v if type(o) == 'table' then s = '{ ' for k,v in pairs(o) do if type(k) ~= 'number' then k = '"'..k..'"' end s = s .. '['..k..'] = ' .. DumpTblInt(v) .. ',' end s=s .. '} ' else s=tostring(o) end return s end function DumpTbl(o) ac_environment("tolog", DumpTblInt(o)) end -- Table copy values function deepcopy(orig) local orig_type = type(orig) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do copy[deepcopy(orig_key)] = deepcopy(orig_value) end setmetatable(copy, deepcopy(getmetatable(orig))) else -- number, string, boolean, etc copy = orig end return copy end -- Returns path,file name and extension. nil=cannot find function SplitFileName(strPathName) local path, fname, ext local i, c, extIndex i=string.len(strPathName) extIndex=i while i>0 do c=string.sub(strPathName, i, i) if ext==nil and c=="." then ext=string.sub(strPathName, i, string.len(strPathName)) extIndex=i-1 elseif fname==nil and (c=="\\" or c=="/") then fname=string.sub(strPathName, i+1, extIndex) path=string.sub(strPathName, 1, i) break end i=i-1 end return path,fname,ext end -- Returns 32bit binary number string "010011100..." function NumToBin(nVal) local nExp, sBin nExp=2147483648 sBin="" repeat if nVal+0.1>=nExp then nVal=nVal-nExp sBin=sBin .. "1" else sBin=sBin .. "0" end nExp=nExp/2 until nExp<0.9 if string.len(sBin)~=32 then error( "NumToBin failed" ) end return sBin end function BitTest(nVal, nBit0based) local s, s2 s=NumToBin(nVal) s2=string.sub(s, 32-nBit0based, 32-nBit0based) if s2=="0" then return 0 end if s2=="1" then return 1 end error( string.format("BitTest failed (s=%s, s2=%s)", s, s2) ) end -- Removes spaces from beg and end function TrimStr(str) local res, start, endIndex, len len=string.len(str) start=1 while start<=len and string.sub(str,start,start)==" " do start=start+1 end endIndex=len while endIndex>0 and string.sub(str,endIndex,endIndex)==" " do endIndex=endIndex-1 end return string.sub(str,start, endIndex) end -- Aiheuttaa virheilmoituksen, jonka Logs osaa tulkita käyttäjälle näytettäväksi viestiksi -- Ilmoitus voi olla tyhjä, jolloin ei näytetä mitään (peruttu) function RaiseError(msg) msg="###>" .. msg .. "<###" error( msg ) end gnCncErrCount=0 -- Notices for the log gbSkipCncErr=false -- Set to true to be silent -- Adds cnc error message to gTblCncErr function AddErrMsg(sLogId, nPos, sText) if nPos==nil then error("AddCncErr/nPos") end if sText==nil then error("AddCncErr/sText") end if gnCncErrCount==0 then gTblCncErr={} end gnCncErrCount=gnCncErrCount+1 gTblCncErr[gnCncErrCount] = {} gTblCncErr[gnCncErrCount].guid=gsCurrentLogGuid gTblCncErr[gnCncErrCount].logid=sLogId gTblCncErr[gnCncErrCount].pos=nPos gTblCncErr[gnCncErrCount].text=sText end -- Adds cnc error message to gTblCncErr function AddCncErr(nPos, sText) if not gbSkipCncErr then AddErrMsg(gsCurrentLogId, nPos, sText) end end gtblUsedIds = {} -- Tänne asetetaan ["id"]=1, että löydetään samalla ID:llä olevat hirret function CheckId( sId ) if sId=="" then local guid guid=ac_objectget("#guid") if guid==nil then guid="" else guid=" GUID=" .. guid end AddCncErr( 0, string.format("LOG/FRAME EMPTY ID FOUND" .. guid) ) --elseif gtblUsedIds[sId]~=null then -- AddCncErr( 0, string.format("LOG/FRAME ID %s USED AT LEAST TWICE", sId) ) else gtblUsedIds[sId]=1 end end -- User entered values may be small if millimeters were used function NormalizeType(nNum) if math.abs(nNum)<0.1 then nNum=math.floor(nNum*1000+0.5) end return nNum end -- Converts metric length to wup-length str max 8 digits function Len2Str(val) local s s=string.format("%.0f", val*1000) if math.abs(val)<0.00049 then s="0" end if string.len(s)>8 then if gnWarnings==nil then gnWarnings=0 end if gnWarnings<100 then toLog( string.format("ERROR: COORDINATE TOO FAR FROM ORIGIN (MAX IS 99999999, PLEASE SET USER ORIGIN AS ANCHOR POINT: %s", s) ) gnWarnings=gnWarnings+1 end end return s end -- Converts int to btl-length str (max 8 digits for now) function Int2Str(val) local s s=string.format("%.0f", val) if string.len(s)>8 then if gnWarnings==nil then gnWarnings=0 end if gnWarnings<100 then toLog( string.format("ERROR: INT-VALUE OUT OF RANGE: %s", s) ) gnWarnings=gnWarnings+1 end end return s end --------------------------------------------------------------------------- -- ARCHIFRAME & ARCHILOGS GEOMETRY FUNCTIONS -- Returns VECTOR x,y,z transformed function MatTransformVec( x, y, z, tblTran ) local xnew, ynew, znew xnew=x*tblTran[1]+ y*tblTran[2]+ z*tblTran[3] ynew=x*tblTran[5]+ y*tblTran[6]+ z*tblTran[7] znew=x*tblTran[9]+ y*tblTran[10]+ z*tblTran[11] return xnew, ynew, znew end -- Returns point x,y,z transformed function MatTransformPt( x, y, z, tblTran ) return MatTransformVec( x+tblTran[4], y+tblTran[8], z+tblTran[12], tblTran ) end -- ### Alustaa muuttujan tranTbl[] pyörittämään ja siirtämään pistettä -- Katso http://gregs-blog.com/category/3d-mathematics/ -- in: -- vecX,vecY,vecZ Vektori, jonka ympärillä pyöritellään. Yksikkövektori!! -- addX,addY,addZ Siirto -- angle Pyörityskulma -- out: -- tranTbl[12] function Rotate3Dinit(vecX, vecY, vecZ, addX, addY, addZ, angle) local cosAngle, sinAngle, ucosAngle, tranTbl cosAngle=math.cos(angle) sinAngle=math.sin(angle) ucosAngle=1-cosAngle tranTbl={} -- Tehdään yksikkövektori --_ulen=1/sqr(vecX*vecX+vecY*vecY+vecZ*vecZ) --vecX=vecX*_ulen --vecY=vecY*_ulen --vecZ=vecZ*_ulen tranTbl[1]=ucosAngle*vecX*vecX+cosAngle tranTbl[2]=ucosAngle*vecX*vecY-vecZ*sinAngle tranTbl[3]=ucosAngle*vecZ*vecX+vecY*sinAngle tranTbl[4]=addX tranTbl[5]=ucosAngle*vecX*vecY+vecZ*sinAngle tranTbl[6]=ucosAngle*vecY*vecY+cosAngle tranTbl[7]=ucosAngle*vecY*vecZ-vecX*sinAngle tranTbl[8]=addY tranTbl[9]=ucosAngle*vecZ*vecX-vecY*sinAngle tranTbl[10]=ucosAngle*vecY*vecZ+vecX*sinAngle tranTbl[11]=ucosAngle*vecZ*vecZ+cosAngle tranTbl[12]=addZ --tranTbl[13]=0 --tranTbl[14]=0 --tranTbl[15]=0 --tranTbl[16]=1 return tranTbl end -- ### Pyörittää 3D-pistettä annetun vektorin ympäri. Kutsu Rotate3Dinit ennen tätä. -- in: -- xin, yin, zin -- out: -- x, y, z function Rotate3D(tranTbl, xin, yin, zin) local x, y, z x=tranTbl[1]*xin + tranTbl[2]*yin + tranTbl[3]*zin + tranTbl[4] y=tranTbl[5]*xin + tranTbl[6]*yin + tranTbl[7]*zin + tranTbl[8] z=tranTbl[9]*xin + tranTbl[10]*yin + tranTbl[11]*zin + tranTbl[12] return x,y,z end -- ### Pyörittää 3D-pistettä annetun vektorin ympäri pitäen edellisen Rotate3DInit-arvot kuosissa -- in: -- xin, yin, zin, angle -- vecX, vecY, vecZ Minkä vektorin ympäri pyöritellään -- out: -- _x, _y, _z function RotateSingle3D(xin, yin, zin, vecX, vecY, vecZ, angle) local x, y, z, tranTbl tranTbl = Rotate3Dinit(vecX, vecY, vecZ, 0, 0, 0, angle) x,y,z=Rotate3D(tranTbl,xin, yin, zin) return x,y,z end -- Calculates cross product of two vectors -- Katsele http://en.wikipedia.org/wiki/Cross_product: On se suunta tälläkin tiedossa: vec1 on oikean käden etusormi, vec2 keskari ja refRes on sitten peukalo ylös -- returns x,y,z function CalcCross3D( x1, y1, z1, x2, y2, z2 ) local x,y,z x = y1 * z2 - z1 * y2 y = z1 * x2 - x1 * z2 z = x1 * y2 - y1 * x2 return x,y,z end -- Sets len to 1 -- Returns x,y,z,bOkVec (true/false=zero len) function ToUnitVec3(x, y, z) local d d=math.sqrt(x*x+y*y+z*z) if d<0.0001 then return 0,0,0,false end d=1/d return x*d,y*d,z*d,true end -- Duunaa tason yhtälön ax+bx+cx+dx=0 tason normaalista ja yhdestä pisteestä x1,y1,z1 -- Huom-- plane constant eri etumerkillä kuin wykobissa -- in: -- tnx, tny, tnz Vektori tason normaalista, lasketaan täällä yksikkövektoriksi -- x1,y1,z1 Piste tasolla -- out: -- ta, tb, tc, td Tason yhtälö (ta,tb,tc=tnx,tny,tnz normalisoituna) function MakePlaneNormal(tnx, tny, tnz, x1, y1, z1) -- template -- inline plane make_plane(const T& x1, const T& y1, const T& z1, -- const T& x2, const T& y2, const T& z2, -- const T& x3, const T& y3, const T& z3) -- { -- plane plane_; -- vector3d v1 = makevector(x2 - x1, y2 - y1, z2 - z1); -- vector3d v2 = makevector(x3 - x1, y3 - y1, z3 - z1); -- plane_.normal = normalize(v1 * v2); -- plane_.constant = dot_product(plane_.normal,makevector(x1,y1,z1)); -- return plane_; -- } local mul, ta, tb, tc, td -- Tässä hypätään suoraan vakion laskentaan mul=1/math.sqrt(tnx*tnx+tny*tny+tnz*tnz) ta=tnx*mul tb=tny*mul tc=tnz*mul td=-(ta*x1+tb*y1+tc*z1) return ta, tb, tc, td end -- ### Suoran ja tason leikkauspiste -- in: -- ta,tb,tc,td Tason yhtälö, a-c=kohtisuora ja d on vakio -- x1, y1, z1 Suora -- x2, y2, z2 Suora -- out: -- hasX Oliko leikkausta -- x, y, z Piste -- dist Etäisyys pisteestä x1,y1,z1 suoran suuntaisesti function LinePlaneX(ta, tb, tc, td, x1, y1, z1, x2, y2, z2 ) -- Tuollainen koodi "käännetty" wykobi-kirjastosta -- template -- inline point3d intersection_point(const line& line, -- const plane& plane) -- { -- vector3d linevec = line[1] - line[0]; -- T denom = dot_product(linevec,plane.normal); -- point3d ipoint = degenerate_point3d(); -- if (not_equal(denom,T(0.0))) -- { -- T t = -distance(line[0],plane) / denom; -- ipoint = line[0] + t * (line[1] - line[0]); -- } -- return ipoint; -- } local x, y, z, dist, denom dist=0 x=x2-x1 y=y2-y1 z=z2-z1 denom=x*ta + y*tb + z*tc -- Pistetulo suora ja tason normaali if math.abs(denom)<1E-5 then -- Tason suuntainen suora return false end -- Pisteen etäisyys tasolta -- (plane.normal.x * point.x + plane.normal.y * point.y + plane.normal.z * point.z ) - plane.constant; dist=-(ta*x1 + tb*y1 + tc*z1 + td)/denom -- Huom-- td eri etumerkillä kuin wykobissa (kun siellä oli puki) x=x1 + x*dist y=y1 + y*dist z=z1 + z*dist return true, x, y, z, dist end -- Returns unit vector 90 degrees left x,y for given vector function GetUnitVecLeft(x, y) local len len=math.sqrt(x*x+y*y) if lenmaxEt then maxEt=cutOffsets[i] end end return cutOffsets, maxEt end -- Calculates intersection of two infinite lines -- Line1 is (x1,y1)->(x2,y2) and line2 is (x3,y3)->(x4,y4) -- See http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ -- Returns three values: -- 1. bool, true=has intersection -- 2. number, x -- 3. number, y function GetLinesX( x1, y1, x2, y2, x3, y3, x4, y4 ) local ua, divisor divisor=(y4-y3)*(x2-x1)-(x4-x3)*(y2-y1) if math.abs(divisor)gnCurrTotLen+1 or y1<-1 or y1>faceHeight+1 or x2<-1 or x2>gnCurrTotLen+1 or y2<-1 or y2>faceHeight+1 or groDepth>1 then AddCncErr( x1, string.format("WARNING: GROOVE WITH SUSPICIOUS BIG COORDINATES/DEPTH - WRITTEN INTO CNC FILE ANYWAY: x1=%.0f y1=%.0f x2=%.0f y2=%.0f depth=%.0f", x1*1000, y1*1000, x2*1000, y2*1000, groDepth*1000) ) end --toLog( string.format( "GRO1, x1=%.3f y1=%.3f x2=%.3f y2=%.3f groDepth==%.3f groWidth=%.3f groAngle=%.3f groFace=%.3f groAngleLen=%.3f", x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen ) ) if gnBvnYoffset~=0 then -- Groove coordinates are always to whole log but we have half log raw material. if groFace==2 or groFace==4 or groFace==5 or groFace==6 then y1=y1-gnBvnYoffset y2=y2-gnBvnYoffset end end if false then -- Try birds mouth if WupAddGroAsBird(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) then return end -- As V-cut at the end if WupAddGroAsVCut(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) then return end end if groFace>4 then if groFace==5 then pos=BvnGetXc(0) else pos=BvnGetXc(gnCurrTotLen) end AddCncErr( pos, "ERROR: WUP DOES NOT SUPPORT GROOVE AT THE END OF THE PIECE, PLEASE FORCE THE GROOVE TO ONE OF THE SIDES." ) return end if math.abs(x1-x2)>0.001 or math.abs(y2-y1)<0.001 or math.abs(groAngle)>EPS or math.abs(groAngleLen)>EPS then AddCncErr( x1, "ERROR: WUP SUPPORTS ONLY STRAIGHT GROOVES WITHOUT TILT/ROTATION ANGLE, GROOVE SKIPPED" ) return end if y1>y2 then AddCncErr( pos, "ERROR: WUP GROOVE Y1>Y2, GROOVE SKIPPED" ) return end local ty, tz if bDepthToY then ty=nDepthMul*groDepth tz=y2-y1 else ty=y2-y1 tz=nDepthMul*groDepth end s=string.format("%s %s, %s, %s, %s", sWupCode, Len2Str(x1-groWidth), Len2Str(groWidth), Len2Str(ty), Len2Str(tz)) WupAddMc( x1-groWidth, s ) end ----------------------------------------------------------------------------- -- FRAME PLANK -- Returns width,height function BvnGetPlankSize() local width,height width=ac_objectget("iWidth") if width==nil or width==0.0 then width=ac_objectget("A") end height=ac_objectget("iHeight") if height==nil or height==0.0 then height=ac_objectget("B") end return width,height end -- Gets frame plank basic parameters: -- gsCurrentLogId Use same global as ArchiLogs to ease showing error msg -- gnCurrTotLen -- gnCurrHeight -- gnCurrWidth -- gnOverlap Always zero function BvnGetPlankBasicParams(unidPlank) local dx, dy, dz, cosTiltAngle, tiltAngle gsCurrentLogId=ac_objectget("#id") gsCurrentLogGuid=unidPlank -- Pituus täytyy laskea tiltAngle=ac_objectget("iTiltAngle") cosTiltAngle=math.cos(tiltAngle) if math.abs(cosTiltAngle)<0.005 then -- Vertical gnCurrTotLen=ac_objectget("zzyzx") else dx=ac_objectget("iEndX")-ac_objectget("iBegX") dy=ac_objectget("iEndY")-ac_objectget("iBegY") dz=math.tan(tiltAngle)*math.sqrt(dx*dx + dy*dy) gnCurrTotLen=math.sqrt(dx*dx+dy*dy+dz*dz) end gnCurrWidth,gnCurrHeight=BvnGetPlankSize() gnOverlap=0 -- ac_objectget("iMatOverlap") gnBvnYoffset=0 if gnOverlap==nil then gnOverlap=0 end gnProfHeight=gnCurrHeight+gnOverlap gnBvnYoffset=0 -- Used for half logs end gsWupChars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-/" function ToWupStr(s) local res, len, i1, c len=string.len(s) res="" for i1=1,len do c=string.sub(s,i1,i1) if string.find(gsWupChars, c, 1, true) then res=res .. c end end return res end -- Warns about impossible cuts -- cutLineSide 1-4 the target side -- tblCutLines Cutting lines projected to target side -- coordEnd 0 or plank length -- sBegEnd "beg" or "end" for warnings -- Returns table of points for ending cuts (in order of y-coordinates): -- nil straight cut -- tbl[2] y1<0.001 and y2>width-0.001 -- tbl[3] y1<0.001, y2 between and y3>width-0.001 function GetEndPoints(cutLineSide, tblCutLines, coordEnd, sBegEnd) local tblPoints={} local i1, i2, v if #tblCutLines==0 then return nil end i1=1 i2=1 while tblCutLines[i1] do tblPoints[i2]={} tblPoints[i2].x=tblCutLines[i1].x1 tblPoints[i2].y=tblCutLines[i1].y1 i2=i2+1 tblPoints[i2]={} tblPoints[i2].x=tblCutLines[i1].x2 tblPoints[i2].y=tblCutLines[i1].y2 i2=i2+1 i1=i1+1 end -- Sort by y table.sort(tblPoints, function (n1, n2) return n1.y < n2.y end) -- Join similar points into new table local tblJoined={} i1=1 i2=0 while tblPoints[i1] do if i2<1 or math.abs(tblJoined[i2].x - tblPoints[i1].x)>0.001 or math.abs(tblJoined[i2].y - tblPoints[i1].y)>0.001 then i2=i2+1 tblJoined[i2]=tblPoints[i1] end i1=i1+1 end -- Check if possible local faceDy faceDy=gnCurrWidth if cutLineSide==2 or cutLineSide==4 then faceDy=gnProfHeight end if i2==2 then if tblJoined[1].y<0.001 and tblJoined[2].y>faceDy-0.001 then -- Full cut ok elseif tblJoined[1].y<0.001 and tblJoined[2].y0.0009 and math.abs(tblJoined[1].x-coordEnd)<0.001 and tblJoined[2].y>faceDy-0.001 then -- Single cut not whole plank, add first point tblJoined[3]=tblJoined[2] tblJoined[2]=tblJoined[1] tblJoined[1]={} tblJoined[1].x=coordEnd tblJoined[1].y=0 else AddCncErr( coordEnd, string.format("Unsupported %s shape 2 - written as straight", sBegEnd) ) return nil end elseif i2==3 and (tblJoined[1].y>0.001 or tblJoined[3].y0 do x,y,z=SidePointToElem(currSide, pointsBeg[i].x, pointsBeg[i].y, tranElem) s=string.format("%s%s,%s, ", s, Len2Str(x), Len2Str(y)) i=i-1 end for i=1,nEnd do x,y,z=SidePointToElem(currSide, pointsEnd[i].x, pointsEnd[i].y, tranElem) s=string.format("%s%s,%s, ", s, Len2Str(x), Len2Str(y)) end else -- Collect cw for i=1,nBeg do x,y,z=SidePointToElem(currSide, pointsBeg[i].x, pointsBeg[i].y, tranElem) s=string.format("%s%s,%s, ", s, Len2Str(x), Len2Str(y)) end i=nEnd while i>0 do x,y,z=SidePointToElem(currSide, pointsEnd[i].x, pointsEnd[i].y, tranElem) s=string.format("%s%s,%s, ", s, Len2Str(x), Len2Str(y)) i=i-1 end end return s end function CncTblToStr() local s, s2 -- From table to text if gTblCnc==nil then return "" end -- Sort the table and create full text table.sort(gTblCnc, function (n1, n2) if math.abs(n1.pos-n2.pos)>EPS then return n1.pos < n2.pos -- compare the sort key end return n1.count < n2.count -- Keep the order if at same pos end) s="" for i,v in ipairs(gTblCnc) do s2 = v.text .. ";\r\n" s=s .. s2 end return s end gtblPlankEdges=nil -- If set, the left and right edges are saved here into fields left, right, vecx, ptr. vecx is x-axis' direction vector in master element coordinates gnPlankEdges=0 -- Write ArchiFrame plank WUP-code -- ac_objectopen() must have been called before calling this -- plankInfo Info for current plank -- elemData "#af_elemdata" for current plank -- tranElem Transformation matrix from global 3D to current element coordinate system -- elemlayer Current element layer -- sPanelLayer nil=core layer, SLA1-10/SLIx/PLAx/PLIx name of the layer AND no warnings about cnc -- Returns table that contains fields: -- cnc The wup cnc-text -- x1,y1 The wup-anchor coordinate -- vecx X-axis vector projected to tranElem -- comp Component name OG, UG... -- sortstr UG=001, OG=002, others are 003 function WriteWupPlank(unidPlank, plankInfo, elemData, tranElem, elemlayer, sPanelLayer) local s, n, i, nOther, x, y, z, nDepth, x1, x2, dx, dy, nWidth, sBin, sPart, sMat local bBegJoint, bEndJoint, nLeftRight local nBtlSide, bMirrorY local nFirstStopMark=nil -- Need to add stop mark to begin if there is not one already local nLastStopMark=nil -- Need to add stop mark to end if there is not one already local y1Stop, y2Stop local cutLineSide local tblCutLinesBeg={} local tblCutLinesEnd={} local by, hz, prevWarn prevWarn=gbSkipCncErr gbSkipCncErr=false if sPanelLayer then gbSkipCncErr=true end BvnGetPlankBasicParams(unidPlank) if not sPanelLayer and not gtblPlankEdges then CheckId(gsCurrentLogId) end by=gnCurrWidth hz=gnProfHeight if math.abs(elemData.rotangle)<1 then cutLineSide=1 -- Top elseif math.abs(elemData.rotangle-90)<1 then cutLineSide=4 -- Back hz=gnCurrWidth by=gnProfHeight elseif math.abs(elemData.rotangle-180)<1 or math.abs(elemData.rotangle+180)<1 then cutLineSide=3 -- Bottom elseif math.abs(elemData.rotangle-270)<1 or math.abs(elemData.rotangle+90)<1 then cutLineSide=2 -- Front hz=gnCurrWidth by=gnProfHeight else AddCncErr( 0, string.format("Unsupported rotation angle %f - plank skipped", elemData.rotangle) ) return nil end x,y,z=MatTransformVec( plankInfo.vecx.x, plankInfo.vecx.y, plankInfo.vecx.z, tranElem ) if math.abs(z)>0.01 then AddCncErr( 0, string.format("Plank tilt in element is unsupported in wup (z vector=%f)- part skipped", z) ) return nil end if gnBvnLogDir~=EBvnDirBeg then RaiseError( "WUP writing having plank\'s end as reference point unsupported now" ) end gnCncCount=0 gTblCnc=nil gsCncText="" -- Frame plank has all machinings in single table including beginning and ending shapes -- Plank direction in element coordinates local xx, xy, xz, comp, sortstr local yx, yy, yz, ydir local wupx1, wupy1 local currSide, zoff currSide=plankInfo.tblSides[cutLineSide] xx, xy, xz=MatTransformVec( currSide.vecx.x, currSide.vecx.y, currSide.vecx.z, tranElem) yx, yy, yz=MatTransformVec( currSide.vecy.x, currSide.vecy.y, currSide.vecy.z, tranElem) ydir=ac_geo("linedist", 0, 0, xx, xy, yx, yy) -- Negative=y-axis to left, positive=y-axis to right if ydir<0 then wupx1,wupy1,zoff=SidePointToElem(currSide, 0, by, tranElem) else wupx1,wupy1,zoff=SidePointToElem(currSide, 0, 0, tranElem) end if gtblPlankEdges then local tblNew tblNew={} tblNew.ptr=plankInfo.ptr tblNew.right={} tblNew.right.x1, tblNew.right.y1=SidePointToElem(currSide, 0, 0, tranElem) tblNew.right.x2, tblNew.right.y2=SidePointToElem(currSide, plankInfo.len, 0, tranElem) tblNew.left={} tblNew.left.x1, tblNew.left.y1=SidePointToElem(currSide, 0, by, tranElem) tblNew.left.x2, tblNew.left.y2=SidePointToElem(currSide, plankInfo.len, by, tranElem) tblNew.vecx={} tblNew.vecx.x=xx tblNew.vecx.y=xy tblNew.vecx.z=xz gnPlankEdges=gnPlankEdges+1 gtblPlankEdges[gnPlankEdges]=tblNew end -- # Machinings n=ac_objectget("iMc" ,-1) for i=1,n do nType=NormalizeType(ac_objectget("iMc", i, 1)) if nType==EMcFrAngledBeg or nType==EMcFrAngledEnd then local isBeg local nAngleSide, nAngleTop, tblLine isBeg=(nType==EMcFrAngledBeg) -- Check if full plank cut local tblInfo, tblRes tblInfo={} tblInfo.guid = gsCurrentLogGuid tblInfo.mcindex = i tblInfo.cutlineside=cutLineSide tblRes = af_request("mc_getcutinfo", tblInfo) nAngleTop=ac_objectget("iMc", i, 2) -- In degrees nAngleSide=ac_objectget("iMc", i, 3) -- Bevel s="beg" if not isBeg then s="end" end if cutLineSide==1 or cutLineSide==3 then if math.abs(nAngleSide-90)>1 then AddCncErr(0, string.format("WARNING: Unsupported cut angle in cut %s - cut not in wup", s)) end else if math.abs(nAngleTop-90)>1 then AddCncErr(0, string.format("WARNING: Unsupported cut angle in cut %s - cut not in wup", s)) end end --DumpTbl(tblRes) tblLine={} tblLine.x1=tblRes.x1 tblLine.y1=tblRes.y1 tblLine.x2=tblRes.x2 tblLine.y2=tblRes.y2 if isBeg then tblCutLinesBeg[#tblCutLinesBeg+1]=tblLine else tblCutLinesEnd[#tblCutLinesEnd+1]=tblLine end elseif nType==EMcFrGroove then x1 =ac_objectget("iMc", i, 2) y1 =ac_objectget("iMc", i, 3) x2 =ac_objectget("iMc", i, 4) y2 =ac_objectget("iMc", i, 5) groDepth=ac_objectget("iMc", i, 6) groWidth=ac_objectget("iMc", i, 7) groAngle=ac_objectget("iMc", i, 8)*PI/180.0 groFace =ac_objectget("iMc", i, 9) groAngleLen=ac_objectget("iMc", i, 10)*PI/180.0 -- Decide the wup target surface here local mul, bDepthToY mul=1 bDepthToY=false if groFace==1+((cutLineSide - 1 + 2) % 4) then s="FRY" mul=-1 elseif groFace==cutLineSide then -- Back side s="FRY" else -- Calculate if on the left or right side of the plank local midx1, midy1, midx2, midy2 midx1,midy1=SidePointToElem(currSide, 0, by*0.5, tranElem) midx2,midy2=SidePointToElem(currSide, gnCurrTotLen, by*0.5, tranElem) x,y=SidePointToElem(plankInfo.tblSides[groFace], 0, 0, tranElem) s="FRZ" bDepthToY=true if ac_geo("linedist", x, y, midx1, midy1, midx2, midy2)<0 then mul=-1 end end WupAddGro(s, mul, bDepthToY, sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) elseif nType==EMcFrDrill then x1 =ac_objectget("iMc", i, 2) y1 =ac_objectget("iMc", i, 3) groWidth=ac_objectget("iMc", i, 4) groDepth=ac_objectget("iMc", i, 5) groFace =ac_objectget("iMc", i, 6) local mul mul=1 bDepthToY=false if groFace==cutLineSide then s="BOZ" mul=-1 elseif groFace==1+((cutLineSide - 1 + 2) % 4) then -- Back side s="BOZ" else -- Calculate if on the left or right side of the plank local midx1, midy1, midx2, midy2 midx1,midy1=SidePointToElem(currSide, 0, by*0.5, tranElem) midx2,midy2=SidePointToElem(currSide, gnCurrTotLen, by*0.5, tranElem) x,y=SidePointToElem(plankInfo.tblSides[groFace], 0, 0, tranElem) s="BOY" bDepthToY=true if ac_geo("linedist", x, y, midx1, midy1, midx2, midy2)<0 then mul=-1 end end -- Inverse always if groFace==1 or groFace==3 then y1=gnCurrWidth-y1 else y1=gnProfHeight-y1 end if groFace>4 then AddCncErr( 0, "WARNING: DRILLING AT THE BEG/END OF PIECE SKIPPED" ) else if groDepth==0 then groDepth=gnCurrWidth if groFace==1 or groFace==3 then groDepth=gnProfHeight end end s=string.format("%s %s, %s, %s, %s", s, Len2Str(x1), Len2Str(y1), Len2Str(groWidth), Len2Str(mul*groDepth)) WupAddMc( x1, s ) end elseif nType==EMcFrMarking and false then x1 =ac_objectget("iMc", i, 2) y1 =ac_objectget("iMc", i, 3) x2 =ac_objectget("iMc", i, 4) y2 =ac_objectget("iMc", i, 5) xPosText=ac_objectget("iMc", i, 6) yPosText=ac_objectget("iMc", i, 7) fontSize=ac_objectget("iMc", i, 8) groFace =ac_objectget("iMc", i, 9) textOff =ac_objectget("iMc", i, 10) nParallel=BitTest(ac_objectget("iMc", i, 11), 1) bLine=true if BitTest(ac_objectget("iMc", i, 11), 2)==1 then bLine=false end s=ac_objectget("iMcStr", i, 1) elseif nType==EMcFrNailLine then -- NOP here elseif nType==EMcFrSaw and false then -- Lengthwise sawing logFace =ac_objectget("iMc", i, 2) xStart =ac_objectget("iMc", i, 3) -- +begExtra dWidth =ac_objectget("iMc", i, 4) dDepthTop =ac_objectget("iMc", i, 5) dYoffTop =ac_objectget("iMc", i, 6) dAngleY2Deg =ac_objectget("iMc", i, 7) dAngleY1Deg =ac_objectget("iMc", i, 8) --BtlAddSaw( sPieceNum, logFace, xStart, dWidth, dDepthTop, dYoffTop, dAngleY2Deg, dAngleY1Deg ) elseif nType~=0 then -- WITH WUP THE PLANK GOES TO SAW FIRST: AddCncErr( ac_objectget("iMc", i, 2), string.format("UNSUPPORTED MACHINING %d SKIPPED", nType) ) end end if gnWupIdSide~=0 and gsCurrentLogId~="" and not sPanelLayer then local sMark s=ac_objectget("iUsageId") if s~="" then sMark=string.format( "%s (%s)", gsCurrentLogId, s) else sMark=string.format( "%s", gsCurrentLogId) end if gnWupIdPos<0 then x=-gnWupIdPos if x>gnCurrTotLen*0.5 then x=gnCurrTotLen*0.5 end elseif gnWupIdPos>0 then x=gnCurrTotLen-gnWupIdPos if x0 or nEnd>0 or (math.abs(xx)>0.001 and math.abs(xy)>0.001) or xy<-0.001 or xx<-0.001 or sPanelLayer then -- If something in either end, add two points to other if missing -- Also if not vertical/horizontal -- Also if vertical from top to bottom -- Also if horizontal from right to left if nBeg==0 then pointsBeg={} pointsBeg[1]={} pointsBeg[1].x=0 pointsBeg[1].y=0 pointsBeg[2]={} pointsBeg[2].x=0 pointsBeg[2].y=by nBeg=2 end if nEnd==0 then pointsEnd={} pointsEnd[1]={} pointsEnd[1].x=gnCurrTotLen pointsEnd[1].y=0 pointsEnd[2]={} pointsEnd[2].x=gnCurrTotLen pointsEnd[2].y=by nEnd=2 end end local sId sId=gsCurrentLogId if sId=="" then sId="(ID)" end --zoff=math.abs(elemlayer.thickness-(zoff+hz)) if nBeg>2 or nEnd>2 or sPanelLayer then -- Use BT6 if nBeg<3 then pointsBeg[3]=pointsBeg[2] -- Duplicate point end if nEnd<3 then pointsEnd[3]=pointsEnd[2] -- Duplicate point end comp="BT6" if sPanelLayer then comp=sPanelLayer end s=string.format( "%s %s, %s, %s, %s1, %s, %s", comp, Len2Str(gnCurrTotLen), Len2Str(by), Len2Str(hz), GetBtPoints(currSide, pointsBeg, pointsEnd, tranElem), ToWupStr(sId), Len2Str(zoff) ) elseif nBeg==2 and nEnd==2 then comp="BT4" s=string.format( "BT4 %s, %s, %s, %s1, %s, %s", Len2Str(gnCurrTotLen), Len2Str(by), Len2Str(hz), GetBtPoints(currSide, pointsBeg, pointsEnd, tranElem), ToWupStr(sId), Len2Str(zoff) ) else if nBeg>0 or nEnd>0 then AddCncErr( 0, "WARNING: Unknown end types - written as straight" ); end -- Select the type based on orientation and usage comp=ac_objectget("iElemGroup") if math.abs(xy)>math.abs(xx) then -- Vertical comp="QS" else -- Actually here we need to write lower left in elem coordinates if (ydir<0 and xx<0) or (ydir>0 and xx>0) then wupx1,wupy1,z=SidePointToElem(currSide, 0, by, tranElem) else wupx1,wupy1,z=SidePointToElem(currSide, 0, 0, tranElem) end if string.match(comp, "^top_%a*") then comp="OG" sortstr="002" elseif string.match(comp, "^bottom_%a*") then comp="UG" sortstr="003" else comp="LS" end end s=string.format( "%s %s, %s, %s, %s, %s, 1, %s, %s", comp, Len2Str(gnCurrTotLen), Len2Str(by), Len2Str(hz), Len2Str(wupx1), Len2Str(wupy1), ToWupStr(sId), Len2Str(zoff) ) end local tblRes={} s = s .. ";\r\n" .. CncTblToStr() tblRes.cnc=s tblRes.comp=comp tblRes.x1=wupx1 tblRes.y1=wupy1 tblRes.vecx={} tblRes.vecx.x=xx tblRes.vecx.y=xy tblRes.vecx.z=xz gbSkipCncErr=prevWarn return tblRes end -- Called before writing new cnc-file - resets globals function BvnOnInit() gnCncErrCount=0 gtblUsedIds = {} gnBvnPieceNum=1 gnBoardPieceNum=500000 end -- CODE SHARED WITH ARCHIFRAME END <-- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- -- ARCHIFRAME ONLY -- Called before save as dialog is showed -- strPlnFileName -> Full path file name of current pln WITHOUT extension (.pln removed) -- Return values (multiple ret values): -- 1=listing file name WITH extension, ""=Just prompt with file dlg, starting with *=result contains full path name - do not prompt -- 2=file extension to be used in save as dialog function OnInit(strPlnFileName) local projNum = ac_environment("parsetext", "") BvnOnInit() sFileName=projNum .. ".wup" sExt="wup" return sFileName, sExt end function SettingsDlg() local tblSettings tblSettings={} tblSettings[1] ={} tblSettings[1].cfgonly =1 tblSettings[1].type =1 tblSettings[1].prompt ="Write plank id to wup side 1-4 (0=none)" tblSettings[1].key ="wupmarkside" tblSettings[1].defvalue =1 tblSettings[1].valuelist="\"1:None\",\"2:E1\",\"2:E2\",\"4:E3\",\"5:E4\"" tblSettings[2] ={} tblSettings[2].cfgonly =1 tblSettings[2].type =3 tblSettings[2].prompt ="ID pos (neg=dist from beg, 0=center, pos=from end)" tblSettings[2].key ="wupmarkpos" tblSettings[2].defvalue =-0.5 tblSettings[3] ={} tblSettings[3].cfgonly =0 tblSettings[3].type =2 tblSettings[3].prompt ="Element built from inside to out (layer numbers)" tblSettings[3].key ="wupinout" tblSettings[3].defvalue =0 tblSettings[4] ={} tblSettings[4].cfgonly =1 tblSettings[4].type =1 tblSettings[4].prompt ="Extend selection" tblSettings[4].key ="wupextsel" tblSettings[4].defvalue =1 tblSettings[4].valuelist="\"1:No - write just selected pieces\",\"2:Write all layers & pieces related to selection\"" tblSettings[5] ={} tblSettings[5].cfgonly =0 tblSettings[5].type =1 tblSettings[5].prompt ="Cuts at openings' top & bottom" tblSettings[5].key ="wupopeningtopbot" tblSettings[5].defvalue =gnWupOpeningBotTop+1 tblSettings[5].valuelist="\"1:No\",\"2:Win top&bottom, door tops for cladding\"" tblSettings[6] ={} tblSettings[6].cfgonly =1 tblSettings[6].type =3 tblSettings[6].prompt ="Sawing extra depth to layer thickness boards" tblSettings[6].key ="wupsawextraboard" tblSettings[6].defvalue =gnWupSawExtraDepthBoard tblSettings[7] ={} tblSettings[7].cfgonly =1 tblSettings[7].type =3 tblSettings[7].prompt ="Sawing extra depth to layer thickness others" tblSettings[7].key ="wupsawextra" tblSettings[7].defvalue =gnWupSawExtraDepth bRes,sErr=ac_optiondlg("LDWW", "WUP-writing settings", tblSettings) if not bRes then return false end gnWupIdSide=tblSettings[1].value-1 gnWupIdPos=tblSettings[2].value gbElemLayerInToOut=false if tblSettings[3].value~=0 then gbElemLayerInToOut=true end gnWupExpandSel =tblSettings[4].value-1 gnWupOpeningBotTop =tblSettings[5].value-1 gnWupSawExtraDepthBoard=tblSettings[6].value gnWupSawExtraDepth =tblSettings[7].value return true end -- Returns table: -- map from element guid -> 1-based table of planks belonging to the element (planks are in the original order sorted by id) function FindElemsFromPlanks(tblPlanks) local i,v local tblGuids, tblInfo, tbl tblGuids={} mapElemToPlanks={} for i,v in ipairs(tblPlanks) do ac_objectopen(v) tblInfo=af_request("plankinfo") if tblInfo.ownerelemguid==nil then AddErrMsg(ac_objectget("#id"), 0, "Plank not part of an element - skipped") else tbl=tblGuids[tblInfo.ownerelemguid] if tbl==nil then tbl={} tblGuids[tblInfo.ownerelemguid]=tbl end tbl[#tbl+1]=v end ac_objectclose() end return tblGuids end -- nExtendSel 0=build just from tblPlanks, 1=extend to contain everything in the elements -- Groups the elements by parent guid and sorts by elemid, returned table is 1-based and each item has fields as af_request("elem_openparent") and additional: -- elemid The AC info-text, element ID function GroupElements(mapElemToPlanks, nExtendSel) local i, v, i2, v2 local tblElemMaster, tblInfo, nElems, tblHandled, mapElemToPlanksNew mapElemToPlanksNew={} tblElemMaster={} tblHandled={} nElems=0 for i,v in pairs(mapElemToPlanks) do if not tblHandled[i] then tblInfo=af_request("elem_openparent", i) ac_objectopen(tblInfo.guid) tblInfo.elemid=ac_objectget("#id") ac_objectclose() af_request("elem_closeparent", 0) nElems=nElems+1 tblElemMaster[nElems]=tblInfo for i2,v2 in pairs(tblInfo.tblelems) do tblHandled[v2.guid]=true if nExtendSel==1 then -- Get all planks from the layer local tbl, i3, v3, tblElemPlanks tbl=af_request("elem_quantities", v2.guid, 0).tblelems[1].tblplanks if tbl then tblElemPlanks={} for i3,v3 in ipairs(tbl) do tblElemPlanks[i3]=v3.guid end mapElemToPlanksNew[v2.guid]=tblElemPlanks end end end end end table.sort(tblElemMaster, function (n1, n2) return n1.elemid < n2.elemid end) if nExtendSel==0 then mapElemToPlanksNew=mapElemToPlanks end return tblElemMaster,mapElemToPlanksNew end function GetElemFileName(strFileName, strAdd) local path, fname, ext path, fname, ext=SplitFileName(strFileName, strNum) return string.format("%s%s%s%s", path, fname, strAdd, ext) end -- Called to do all that needs to be done -- strFileName is full path name for the result file function OnSaveList(strFileName) -- Edit BVN-settings here gnBvnPlateSide=EBvnMale gnBvnLogDir=EBvnDirBeg gnBvnPieceNum=nil gBtlForceId=nil gnUid=1 gnWarnings=0 gbElemLayerInToOut=false if not SettingsDlg() then gbCancel=true return end -- Get all selected elements. Now gbPlanksSel tells if there were planks selected and gTblPlanks is the table of planks to process (no boards) local mapElemToPlanks, tblElemGroups, i, v mapElemToPlanks=FindElemsFromPlanks(gTblPlanks) tblElemGroups,mapElemToPlanks=GroupElements(mapElemToPlanks, gnWupExpandSel) -- Process by multilayer element group local i1, v1, fileName, elemTbl for i1,v1 in pairs(tblElemGroups) do fileName=GetElemFileName(strFileName, "_" .. v1.elemid) elemTbl=af_request("elem_openparent", v1.guid) SaveMultiLayerElem(fileName, v1, mapElemToPlanks) af_request("elem_closeparent", 0) end end -- Gives -- infoOrig "plankinfo" for the board/plank -- tblDrills Drills in parent element coordinates, fields x,y,dia -- tblSawCuts Saw cuts in parent element coordinates, fields x1,y1,x2,y2,angledeg,ptr (ref line at right hand side of the cut as in grooves) -- bNailPoints Write as separate points -- sNailIndex NR index/nail device -- Returns nailing cnc-text function GetNailDrillCnc(tranMaster, infoOrig, tblDrills, tblSawCuts, bNailPoints, sNailIndex) local n, i, nType, hasNail, sCnc, s, x, y, z, currSide sCnc="" n=ac_objectget("iMc" ,-1) for i=1,n do nType=NormalizeType(ac_objectget("iMc", i, 1)) if nType==EMcFrNailGroup then AddCncErr(0, "ERROR: NAIL GROUP MACHINING NOT SUPPORTED") elseif nType==EMcFrNailLine then -- Mc data local x1, y1, x2, y2, z2, cols, rows, anglexRad, angleyRad, spacing, side, len, nails, dx, dy, flags x1 = ac_objectget("iMc", i, 2) y1 = ac_objectget("iMc", i, 3) x2 = ac_objectget("iMc", i, 4) y2 = ac_objectget("iMc", i, 5) spacing = ac_objectget("iMc", i, 6) flags = ac_objectget("iMc", i, 7) side = ac_objectget("iMc", i, 8) dx=x2-x1 dy=y2-y1 len=math.sqrt(dx*dx + dy*dy) if spacing<0.001 then spacing=0.001 end if len<0.010 then -- Single point nails=1 else if BitTest(flags, 2) then nails=1+math.floor((len+0.001)/spacing) else nails=2+math.floor((len-0.001)/spacing) spacing=(len - 0.0001)/(nails-1) end end -- Get geometry for current surface currSide=infoOrig.tblSides[side] if bNailPoints then -- Nail points local nail, invLen, xc, yc if len<0.001 then invLen=1 else invLen=1/len end for nail=0,nails-1 do xc=x1+nail*spacing*invLen*dx yc=y1+nail*spacing*invLen*dy -- Transform to the layer (forget z) x,y,z = SidePointToElem(currSide, xc, yc, tranMaster) -- NR x1,y1,x2,y2,a,i; s = string.format( "NR %s, %s, %s, %s, 1, %s;\r\n", Len2Str(x), Len2Str(y), Len2Str(x), Len2Str(y), sNailIndex ) sCnc = sCnc .. s end else -- Nail lines x1,y1 = SidePointToElem(currSide, x1, y1, tranMaster) x2,y2 = SidePointToElem(currSide, x2, y2, tranMaster) -- NR x1,y1,x2,y2,a,i; s = string.format( "NR %s, %s, %s, %s, %s, %s;\r\n", Len2Str(x1), Len2Str(y1), Len2Str(x2), Len2Str(y2), Len2Str(spacing), sNailIndex ) sCnc = sCnc .. s end elseif nType==EMcFrDrill and tblDrills~=nil then -- Just collect to layer coordinates local dia, depth, side, x2, y2, z2 x =ac_objectget("iMc", i, 2) y =ac_objectget("iMc", i, 3) dia =ac_objectget("iMc", i, 4) depth =ac_objectget("iMc", i, 5) side =ac_objectget("iMc", i, 6) currSide=infoOrig.tblSides[side] if depth>EPS then ac_environment( "tolog", string.format("WARNING: DRILL IN [%s] HAS DEPTH - WILL BE DRILLED THROUGH", ac_objectget("#id")) ) end x,y,z=MatTransformVec( currSide.vecz.x, currSide.vecz.y, currSide.vecz.z, tranMaster ) if math.abs(z)<0.999 then ac_environment( "tolog", string.format("WARNING: DRILL IN [%s] IN BAD SIDE %d - SKIPPED", ac_objectget("#id"), side) ) else -- Get geometry for current surface info=infoOrig.tblSides[side] -- Transform to the layer (forget z) x, y=SidePointToElem(currSide, x, y, tranMaster) local scan, found found=false for scan=1,#tblDrills do if math.abs(tblDrills[scan].x - x)0.010 then ac_environment( "tolog", string.format("WARNING: SAW CUT IN [%s] TOO WIDE (%.0f mm) - WRITTEN AS SAW CUT ONLY", ac_objectget("#id"), width*1000) ) end tbl={} tbl.x1, tbl.y1 = SidePointToElem(currSide, x1, y1, tranMaster) tbl.x2, tbl.y2 = SidePointToElem(currSide, x2, y2, tranMaster) tbl.angledeg = ac_objectget("iMc", i, 8) tbl.ptr=infoOrig.ptr --DumpTbl(tbl) tblSawCuts[#tblSawCuts+1]=tbl end end end return sCnc end -- tranMaster Translation matrix from global coordinates to the element's coordinate system -- elemlayer Layer from af_request("elem_openparent") -- tblPlanks 1-based table of planks to handle for this element sorted by ID -- tblDrills Drills in parent element coordinates -- Returns 1-based index of planks containing fields as WriteWupPlank. Also field cncnail function DoCoreLayer(tranMaster, elemlayer, tblPlanks, tblDrills) local tblRes={} local nRes=0 local i, v, plankInfo, elemData af_request("elem_openlayer", elemlayer.guid) for i,v in ipairs(tblPlanks) do ac_objectopen(v) plankInfo=af_request("plankinfo") elemData=ac_objectget("#af_elemdata") nRes=nRes+1 tblRes[nRes]=WriteWupPlank(v, plankInfo, elemData, tranMaster, elemlayer) if tblRes[nRes] then tblRes[nRes].cncnail=GetNailDrillCnc(tranMaster, plankInfo, tblDrills, nil, false, "20") else nRes=nRes-1 end ac_objectclose() end af_request("elem_closelayer") table.sort(tblRes, function (n1, n2) if n1.sortkey~=n2.sortkey then return n1.sortkey0.001 then return n1.x10.001 then return n1.x10.001 then return n1.elemx10.999 then s=string.format( "%s %s, %s, %s, %s, %s, %s, %s, %s;\r\n", sPla, Len2Str(v.poly.x2-v.poly.x1), Len2Str(v.poly.y2-v.poly.y1), Len2Str(v.thickness), Len2Str(v.elemx1), Len2Str(v.elemy1), matRef, ToWupStr(v.id), Len2Str(zLayer) ) else AddCncErr( 0, string.format("ERROR - PART SKIPPED: Board has unsupported orientation, vector x in element coordinates: %f,%f", x, y) ) s="" end sRes=sRes .. s sCncNails=sCncNails .. v.cncnail end return sRes, sCncNails end -- Swaps first outline's direction (NO ARCS) function SwapContourDir(polyPoints, swapHoles) local i, v, first, last, points, half, temp first=1 while true do i=first while true do v=polyPoints[i] temp=v.overbegin v.overbegin=v.overendin v.overendin=temp if v.isendcontour then last=i break end i=i+1 end -- Move openingtype since it defines next edge's type temp=polyPoints[last].openingtype i=last while i>first do polyPoints[i].openingtype=polyPoints[i-1].openingtype i=i-1 end polyPoints[first].openingtype=temp -- Swap direction points=last-first+1 half=math.floor(points/2) for i=0,half-1 do temp=polyPoints[first+i] polyPoints[first+i]=polyPoints[last-i] polyPoints[last-i]=temp end -- Move end statuses from old last point to old first point polyPoints[last].isendcontour=polyPoints[first].isendcontour -- true polyPoints[last].isendpolygon=polyPoints[first].isendpolygon polyPoints[first].isendcontour=nil polyPoints[first].isendpolygon=nil if not swapHoles or polyPoints[last].isendpolygon then break end first=last+1 end end -- Checks if line dir from pt1 to pt2 is different from vecNoCuts function IsNoDir(vecNoCuts, pt1, pt2) local dx,dy,len dx=pt2.x-pt1.x dy=pt2.y-pt1.y len=math.sqrt(dx*dx + dy*dy) if len<0.001 then return false end dx=dx/len dy=dy/len if math.abs(dx*vecNoCuts.x+dy*vecNoCuts.y)>0.99 then return true end return false end -- Returns: nil=no special, string=cnc-text to add function CheckSpecCut(tranMaster, currSide, tblEdge, bHole, depth, z, sType, nWupOpeningBotTop) local n, s, c1, c2 n=tblEdge.c1.openingtype if nWupOpeningBotTop==1 and n then c1=tblEdge.c1 c2=tblEdge.c2 if c1.x>c2.x then c1,c2=c2,c1 end if (n==11 or n==21) and math.abs(c1.y - c2.y)<0.001 then -- Door or window top, add two cuts: -- Lower at the opening top-46 mm no angle, extend 49 mm left and right -- Upper at window top and cut with 15 deg angle -- Lower s="" s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,121,0,%s;\r\n", Len2Str(c1.x-0.047), Len2Str(c1.y-0.050), Len2Str(depth), Len2Str(z) ) -- 121: 100=Tool on left, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,121,0,%s;\r\n", Len2Str(c2.x+0.047), Len2Str(c1.y-0.050), Len2Str(depth), Len2Str(z) ) -- Upper s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,221,15, %s;\r\n", Len2Str(c1.x-0.049), Len2Str(c1.y-0.007), Len2Str(depth), Len2Str(z) ) -- 221: 200=Tool on right, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,221,15, %s;\r\n", Len2Str(c2.x+0.049), Len2Str(c1.y-0.007), Len2Str(depth), Len2Str(z) ) return s end if n==13 and math.abs(c1.y - c2.y)<0.001 then -- Window bottom, add two cuts: -- Lower at opening bottom no angle, extend 49 mm left and right -- Upper +46 mm with 15 deg angle, extend 49 mm left and right -- Upper s="" s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,221,15,%s;\r\n", Len2Str(c1.x-0.049), Len2Str(c1.y+0.046-0.003), Len2Str(depth), Len2Str(z) ) -- 221: 200=Tool on right, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,221,15,%s;\r\n", Len2Str(c2.x+0.049), Len2Str(c1.y+0.046-0.003), Len2Str(depth), Len2Str(z) ) -- Lower s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,121,0,%s;\r\n", Len2Str(c1.x-0.049), Len2Str(c1.y), Len2Str(depth), Len2Str(z) ) -- 121: 100=Tool on left, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,121,0,%s;\r\n", Len2Str(c2.x+0.049), Len2Str(c1.y), Len2Str(depth), Len2Str(z) ) return s end end return nil end -- If not psf: PP 1252,1364,-13,221,0,0; -- If yes psf: PP 1252,1364,0,0,0; function GetPP( isPsf, x, y, depth, sControl, z ) if isPsf then return string.format( "PP %s,%s,0,0,0;\r\n", Len2Str(x), Len2Str(y) ) else return string.format( "PP %s,%s,%s,%s,0,%s;\r\n", Len2Str(x), Len2Str(y), Len2Str(depth), sControl, Len2Str(z) ) end end -- Writes the openings plus outline polygon. Does not add the header PSG nor PSF -- tranMaster Translation matrix from global coordinates to the element's coordinate system -- currSide Geometry from plankinfo/tblSides -- polyPoints For example af_request("getpoly", nil, elem.guid). CHANGED HERE -- bHole Is it a hole polygon or contour line -- depth "t" in wup docs -- z "z" in wup docs -- sType PSG 2/PSF -- vecNoCuts Direction vector if no cuts to some directions, nil=no limits -- nWupOpeningBotTop 0=No special opening cuts, other values manufacturer specific -- Returns cnc text function GetLayerPolyWup(tranMaster, currSide, polyPoints, bHole, depth, z, sType, vecNoCuts, nWupOpeningBotTop) local s, i, v, sCnc, begInd, prev, p15, endv, nToolSide local tblLines, nLines, tblLine prev=nil begInd=1 if bHole then SwapContourDir(polyPoints, false) -- Actually this is called with just single contour line since openings are cut first end -- First collect lines (to be able to change those to opening top/bottom cuts and to filter out vecNoCuts with simpler logic) -- c1 has always code isendcontour. c2 is then the contour begin pt tblLines={} nLines=0 for i,v in ipairs(polyPoints) do -- Calc current point, tamper original poly v.x,v.y=SidePointToElem(currSide, v.x, v.y, tranMaster) if prev then -- if prev.overbegin<=0.001 and v.overendin<=0.001 then -- p15=4 -- Over at both ends -- elseif prev.overbegin<=0.001 and v.overendin>0.001 then -- p15=2 -- Over at begin -- elseif prev.overbegin>0.001 and v.overendin<=0.001 then -- p15=3 -- Over at end -- else -- p15=1 -- No over -- end tblLine={} tblLine.cutfromobj=prev.cutfromobj tblLine.c1=prev tblLine.c2=v tblLine.overcut1=(prev.overbegin<0.010) -- tooldia=0.050 tblLine.overcut2=(v.overendin<0.010) nLines=nLines+1 tblLines[nLines]=tblLine end prev=v if v.isendcontour then -- Duplicate start point, coordinates already changed endv=polyPoints[begInd] tblLine={} tblLine.cutfromobj=v.cutfromobj tblLine.c1=v tblLine.c2=endv tblLine.overcut1=(v.overbegin<0.010) -- tooldia=0.050 tblLine.overcut2=(endv.overendin<0.010) nLines=nLines+1 tblLines[nLines]=tblLine if v.isendpolygon then break end ac_environment("tolog", "WARNING: POLYGON WITH HOLES WRITTEN - CASE NOT TESTED ***"); begInd=i+1 prev=nil end end -- Process the lines local nextprev, isPsf sCnc="" i=1 prev=nil -- Set if c1 written at last round isPsf=false if sType=="PSF" then isPsf=true end local sControl if isPsf then -- This code writes as polygon - better to write as individual lines except for psf/no nailings area -- 221: 200=Tool on the right, [20=undercut, 10=overcut], 1=Cylindrical trimmer if bHole then sControl="221" else sControl="211" -- 211: 200=Tool on the right, , 1=Cylindrical trimmer end while i<=nLines do v=tblLines[i] if v.cutfromobj~=nil then ac_environment("tolog", "ERROR: GetLayerPolyWup/v.cutfromobj~=nil. CONTACT SUPPORT" ) end nextprev=v if vecNoCuts and IsNoDir(vecNoCuts, v.c1, v.c2) then -- Flush last line if any points written if prev then --s=string.format( "PP %s,%s,%s,%s,0,%s;\r\n", Len2Str(prev.c2.x), Len2Str(prev.c2.y), Len2Str(depth), sControl, Len2Str(z) ) s=GetPP(isPsf, prev.c2.x, prev.c2.y, depth, sControl, z) sCnc=sCnc .. s end nextprev=nil else s=CheckSpecCut(tranMaster, currSide, v, bHole, depth, z, sType, nWupOpeningBotTop) if s then -- Flush if prev then --sCnc=sCnc .. string.format( "PP %s,%s,%s,%s,0,%s;\r\n", Len2Str(prev.c2.x), Len2Str(prev.c2.y), Len2Str(depth), sControl, Len2Str(z) ) sCnc=sCnc .. GetPP(isPsf, prev.c2.x, prev.c2.y, depth, sControl, z) end nextprev=nil sCnc=sCnc .. s else -- Write starting point if not prev then sCnc=sCnc .. string.format( "%s;\r\n", sType ) end --s=string.format( "PP %s,%s,%s,%s,0,%s;\r\n", Len2Str(v.c1.x), Len2Str(v.c1.y), Len2Str(depth), sControl, Len2Str(z) ) s=GetPP(isPsf, v.c1.x, v.c1.y, depth, sControl, z) sCnc=sCnc .. s if v.c1.isendcontour then -- Write also c2 --s=string.format( "PP %s,%s,%s,%s,0,%s;\r\n", Len2Str(v.c2.x), Len2Str(v.c2.y), Len2Str(depth), sControl, Len2Str(z) ) s=GetPP(isPsf, v.c2.x, v.c2.y, depth, sControl, z) sCnc=sCnc .. s nextprev=nil end end end prev=nextprev i=i+1 end else -- Write as separate lines local nControl, controlNow, overCut, underCut, write -- 221: 200=Tool on the right, [20=undercut, 10=overcut], 1=Cylindrical trimmer overCut=10 underCut=20 if bHole then -- This is other way round - hole polygons processed overCut=20 underCut=10 end nControl=201 while i<=nLines do v=tblLines[i] write=false if not vecNoCuts or not IsNoDir(vecNoCuts, v.c1, v.c2) then write=true end if v.cutfromobj~=nil then --ac_environment("tolog", string.format("v.cutfromobj=%d", v.cutfromobj)) -- This overrides previous decision write=false if v.cutfromobj==1 then write=true end end if write then s=nil if v.cutfromobj==nil then s=CheckSpecCut(tranMaster, currSide, v, bHole, depth, z, sType, nWupOpeningBotTop) end if s then sCnc=sCnc .. s else -- Write whole line sCnc=sCnc .. string.format( "%s;\r\n", sType ) -- BEG controlNow=nControl+underCut -- Undercut if v.overcut1 then controlNow=nControl+overCut -- overcut end s=GetPP(isPsf, v.c1.x, v.c1.y, depth, string.format("%d", controlNow), z) sCnc=sCnc .. s -- END controlNow=nControl+underCut -- Undercut if v.overcut2 then controlNow=nControl+overCut -- overcut end s=GetPP(isPsf, v.c2.x, v.c2.y, depth, string.format("%d", controlNow), z) sCnc=sCnc .. s end end i=i+1 end end return sCnc end gtblCnc=nil -- If set, will be collected into the buf. Otherwise to io.write gnCnc=0 -- Index to previous if collecting function WriteCnc( s ) if gtblCnc then gnCnc=gnCnc+1 gtblCnc[gnCnc]=s else io.write(s) end end function GetSawCutCnc(sType, vsaw, depth) local s, z z=0 s="" s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,121,%s, %s;\r\n", Len2Str(vsaw.x1), Len2Str(vsaw.y1), Len2Str(depth), Int2Str(vsaw.angledeg), Len2Str(z) ) s=s .. string.format( "PP %s,%s,%s,121,%s, %s;\r\n", Len2Str(vsaw.x2), Len2Str(vsaw.y2), Len2Str(depth), Int2Str(vsaw.angledeg), Len2Str(z) ) -- 121: 100=Tool on left (200=right), 20=undercut, 1=saw blade return s end function GetPolyMid(points) local i, v, x1, y1, x2, y2 x1=1e100 y1=x1 x2=-x1 y2=-x1 for i,v in ipairs(points) do if v.xx2 then x2=v.x end if v.y>y2 then y2=v.y end end return (x1+x2)*0.5, (y1+y2)*0.5 end -- Writes single layer (either planks or boards) function WriteElemLayer(tranMaster, vlayer, sBoardLayer, sPanelLayer, mapElemToPlanks, bCore) local infoOwner, quant, tblDrills, sCncNails, s, plankInfo, zLayer, vecNoCuts infoOwner=af_request("plankinfo", vlayer.guid) quant=af_request("elem_quantities", vlayer.guid, 0).tblelems[1] -- For this single layer only plankInfo=af_request("plankinfo", vlayer.guid) s,s,zLayer=SidePointToElem(plankInfo.tblSides[4], 0, 0, tranMaster) tblDrills={} sCncNails="" -- Handle planks tblPlanks=mapElemToPlanks[vlayer.guid] if quant.layertypenum==2 and quant.tblboards and tblPlanks==nil then -- Board should be exploded into planks ac_environment( "tolog", string.format("ERROR: PANEL MUST BE EXPLODED CURRENTLY: ID=%s. PANELS NOT WRITTEN", quant.tblboards[1].id) ) end vecNoCuts=nil -- If there is paneling no cuts on x-axis of the panel if tblPlanks then if bCore then tblPlanks=DoCoreLayer(tranMaster, vlayer, tblPlanks, tblDrills) else tblPlanks=DoExtraPlankLayer(tranMaster, vlayer, tblPlanks, tblDrills, sPanelLayer) end local kplank, vplank for kplank, vplank in ipairs(tblPlanks) do WriteCnc(vplank.cnc) if vplank.cncnail then sCncNails=sCncNails .. vplank.cncnail end end if quant.layertypenum==2 and tblPlanks and tblPlanks[1] then vecNoCuts=tblPlanks[1].vecx end end --ac_environment("tolog", string.format("elemtype=%s, tblboards=%s", quant.elemtype, tostring(quant.tblboards))) local tblPoly, tblSettings, s, s2, tblSawCuts tblSawCuts={} if quant.tblboards then -- If paneling, take the tblSawCuts from the board object(s) s,s2 = DoBoardsLayer(tranMaster, vlayer, quant.tblboards, sBoardLayer, sPanelLayer, "28", zLayer, tblDrills, tblSawCuts) if quant.layertypenum==1 then -- Board layer, swite SLAs and nails WriteCnc( s ) sCncNails=sCncNails .. s2 -- Nailings between board definitions and before opening cuts WriteCnc( sCncNails ) sCncNails="" end end if quant.layertypenum>=1 then -- Board or panel -- Hole polygons from owning element object or from board objects local i, nWupOpeningBotTop, depth, fromboard local tblSources, swapHoleDir nWupOpeningBotTop=0 if gnWupOpeningBotTop==1 and quant.layertypenum==2 then -- Paneling nWupOpeningBotTop=gnWupOpeningBotTop end fromboard=0 tblSources={} -- [1]-based cells free data containing field .guid tblSources[1]=vlayer -- Element source (fromboard may be 1) swapHoleDir=false if quant.layertypenum==1 then -- Board layer fromboard=1 -- Take from edited boards instead of element object elseif quant.layertypenum==2 and quant.tblboards then -- Paneling layer local val fromboard=1 ac_objectopen(quant.tblboards[1].guid) val=ac_objectget("iSawlines") if val~=nil and val>0 then -- User defined cuts fromboard=2 tblSources=quant.tblboards swapHoleDir=true -- Returned other way round end ac_objectclose() end -- Source is either single element object (or polygons from its owned boards) or -- list of board objects having saw cuts definitions local ksource, vsource, infoPoly, cncContour depth=-vlayer.thickness if quant.layertypenum==1 then depth=depth-gnWupSawExtraDepthBoard else depth=depth-gnWupSawExtraDepth end tblSettings={} tblSettings.side=2 tblSettings.tooldia=0.050 tblSettings.givelist=1 cncContour="" for ksource, vsource in ipairs(tblSources) do infoPoly=af_request("plankinfo", vsource.guid) -- Cutting holes tblSettings.fromboard=fromboard tblSettings.holes=1 tblPoly=af_request("getpoly", tblSettings, vsource.guid) -- NOTE: DIRECTION MAY BE WRONG HERE -- Find closest opening polygon for each saw cut local ksaw, vsaw for ksaw,vsaw in ipairs(tblSawCuts) do local x, y, dist, bestDist vsaw.polyindex=nil if vsaw.ptr==infoPoly.ptr and not vsaw.handled then i=1 bestDist=99999 while tblPoly.poly[i] do x,y=GetPolyMid(tblPoly.poly[i]) x,y=SidePointToElem(infoPoly.tblSides[2], x, y, tranMaster) -- Saw cuts already in master coordinates x=(vsaw.x1+vsaw.x2)*0.5 - x y=(vsaw.y1+vsaw.y2)*0.5 - y dist=math.sqrt(x*x + y*y) if dist= 1 -- y1, y2 Y-coordinates of the marking layer (board layer) function GetHorMarkings(tblMarkPoly, tblEdges, nEdges, distLines, y1, y2) local i, v if nEdges==0 then return "" end -- Horizontal panel, sort by y table.sort( tblEdges, function (t1, t2) local d1, d2 -- Sort non-vertical to last d1=math.abs(t1.vecx.y) d2=math.abs(t2.vecx.y) if d1>0.001 and d2<0.001 then return false end if d1<0.001 and d2>0.001 then return true end return t1.left.y1 < t2.left.y1 end) -- Discard any non-horizontal pieces while nEdges>0 do v=tblEdges[nEdges] if math.abs(v.vecx.y)<0.001 then break end gsCurrentLogId="" AddCncErr( 0, string.format("Non-horizontal piece skipped from markings (x1=%.0f y1=%.0f x2=%.0f y2=%.0f)", 1000*v.left.x1, 1000*v.left.y1, 1000*v.left.x2, 1000*v.left.y2) ) nEdges=nEdges-1 end if nEdges==0 then return "" end -- Make sure right is lower coordinate for i=1,nEdges do v=tblEdges[i] if v.left.y1y2 then break end nLines=nLines+1 tblLines[nLines]=tbl end local sTot -- First row always i=1 sTot="" while true do if tblLines[i].right.y1-0.001>y1 then sTot=sTot .. tblLines[i].right.cnc end if tblLines[i].left.y1+0.001nLines then i=nLines end end return sTot end -- Returns tblLeft, tblRight, nextIndex, left.x1 function WriteVert(tblMarkPoly, tblEdges, nEdges, i1) local i2, prev local left, right left={} left.cnc="" right={} right.cnc="" i2=i1 while i2<=nEdges and math.abs(tblEdges[i1].left.x1 - tblEdges[i2].left.x1)<0.001 do left.cnc=left.cnc .. WriteLinesInPoly( tblMarkPoly, tblEdges[i2].left.x1, tblEdges[i2].left.y1, tblEdges[i2].left.x2, tblEdges[i2].left.y2 ) right.cnc=right.cnc .. WriteLinesInPoly( tblMarkPoly, tblEdges[i2].right.x1, tblEdges[i2].right.y1, tblEdges[i2].right.x2, tblEdges[i2].right.y2 ) i2=i2+1 end left.x1=tblEdges[i1].left.x1 right.x1=tblEdges[i1].right.x1 return left, right, i2, tblEdges[i1].left.x1 end -- distLines >= 1 -- x1, x2 Y-coordinates of the marking layer (board layer) function GetVertMarkings(tblMarkPoly, tblEdges, nEdges, distLines, x1, x2) local i, v if nEdges==0 then return "" end -- Vertical panel, sort by x table.sort( tblEdges, function (t1, t2) local d1, d2 -- Sort non-vertical to last d1=math.abs(t1.vecx.x) d2=math.abs(t2.vecx.x) if d1>0.001 and d2<0.001 then return false end if d1<0.001 and d2>0.001 then return true end return t1.left.x1 < t2.left.x1 end) -- Discard any non-vertical pieces while nEdges>0 do v=tblEdges[nEdges] if math.abs(v.vecx.x)<0.001 then break end gsCurrentLogId="" AddCncErr( 0, string.format("Non-horizontal piece skipped from markings (x1=%.0f y1=%.0f x2=%.0f y2=%.0f)", 1000*v.left.x1, 1000*v.left.y1, 1000*v.left.x2, 1000*v.left.y2) ) nEdges=nEdges-1 end if nEdges==0 then return "" end -- Make sure left is smaller coordinate for i=1,nEdges do v=tblEdges[i] if v.left.x1>v.right.x1 then v.left,v.right = v.right,v.left end end -- Build table of columns local tbl, tblLines local nLines nLines=0 tblLines={} i=1 while i<=nEdges and tblEdges[i].left.x1-0.001x2 then break end nLines=nLines+1 tblLines[nLines]=tbl end local sTot -- First row always sTot="" if nLines==0 then return sTot end i=1 while true do if tblLines[i].left.x1-0.001>x1 then sTot=sTot .. tblLines[i].left.cnc end if tblLines[i].right.x1+0.001nLines then i=nLines end end return sTot end -- Scans for panel and airspace layers and makes marking cnc. Before calling the WriteCnc() output should be trashed -- Writes layers starting from coreindex+given dir -- tblelems elemTbl.tblelems -- klayer Core layer -- nDir +-1 -- -- Returns nil or table containing fields: -- layerpre Layer number that this information should be added before -- cnc cnc text function GetExtMarkingCnc( tranMaster, tblelems, mapElemToPlanks, coreInd, nDir ) local klayer, vlayer, sComm local tblPanelEdges, nPanelEdges local tblAirEdges, nAirEdges -- Collect cnc to be discarded afterwards gtblCnc={} gnCnc=0 -- Find paneling klayer=coreInd nPanelEdges=0 while true do klayer=klayer+nDir if klayer<1 or klayer>#tblelems then break end vlayer=tblelems[klayer] if string.match(vlayer.type, "^finish_ext%a*") then -- Process just the first paneling gtblPlankEdges={} gnPlankEdges=0 WriteElemLayer(tranMaster, vlayer, "UNUSED", "UNUSED", mapElemToPlanks, false) tblPanelEdges=gtblPlankEdges nPanelEdges=gnPlankEdges gtblPlankEdges=nil end end -- Find airspace layer local airLayer=nil local markLayer=nil local add klayer=coreInd tblAirEdges={} nAirEdges=0 while true do klayer=klayer+nDir if klayer<1 or klayer>#tblelems then break end vlayer=tblelems[klayer] if string.match(vlayer.type, "^extstud%a*") then -- Markings will go before first studding if not markLayer then markLayer=tblelems[klayer-nDir] end if not airLayer then airLayer=klayer end -- Write only last layer, discard everything collected before tblAirEdges={} nAirEdges=0 gtblPlankEdges={} gnPlankEdges=0 WriteElemLayer(tranMaster, vlayer, "UNUSED", "UNUSED", mapElemToPlanks, false) if gnPlankEdges>0 then local scan for scan=1,gnPlankEdges do add=true if nPanelEdges>0 then -- Take only 90 degrees if math.abs(tblPanelEdges[1].vecx.x*gtblPlankEdges[scan].vecx.x + tblPanelEdges[1].vecx.y*gtblPlankEdges[scan].vecx.y)>0.001 then add=false end end if add then nAirEdges=nAirEdges+1 tblAirEdges[nAirEdges]=gtblPlankEdges[scan] end end end gtblPlankEdges=nil end end gtblCnc=nil if not airLayer or sMarkingCnc=="" then return nil end local sMarkingCnc, s, nextRow, bHor local x, y, dx, dy, tblRes, tblSettings, tblMarkPoly local plankInfo, dummy dx=tblelems[airLayer].xsize dy=tblelems[airLayer].ysize x=tblelems[airLayer].x1off y=tblelems[airLayer].y1off -- Get element polygon to keep markings inside the element tblSettings={} tblSettings.side=2 tblSettings.expand=-0.020 tblRes=af_request("getpoly", tblSettings, markLayer.guid) tblMarkPoly={} tblMarkPoly[1]=tblRes.poly -- Currently single poly only -- Transform marking layer polygon points into element world since the markings lines are written in that world local k,v plankInfo=af_request("plankinfo", markLayer.guid) for k,v in ipairs(tblRes.poly) do v.x,v.y,dummy=SidePointToElem(plankInfo.tblSides[2], v.x, v.y, tranMaster) end sMarkingCnc="" if nPanelEdges>0 then -- Panel markings: first, last and every 50 cm if math.abs(tblPanelEdges[1].vecx.x)>math.abs(tblPanelEdges[1].vecx.y) then sMarkingCnc=sMarkingCnc .. GetHorMarkings(tblMarkPoly, tblPanelEdges, nPanelEdges, gnWupPanelMarkingsDist, y, y+dy) else sMarkingCnc=sMarkingCnc .. GetVertMarkings(tblMarkPoly, tblPanelEdges, nPanelEdges, gnWupPanelMarkingsDist, x, x+dx) end end if nAirEdges>0 then -- Air space markings: first, last and every 50 cm if math.abs(tblAirEdges[1].vecx.x)>math.abs(tblAirEdges[1].vecx.y) then sMarkingCnc=sMarkingCnc .. GetHorMarkings(tblMarkPoly, tblAirEdges, nAirEdges, 1, y, y+dy) else sMarkingCnc=sMarkingCnc .. GetVertMarkings(tblMarkPoly, tblAirEdges, nAirEdges, 1, x, x+dx) end end if sMarkingCnc=="" then return nil end local zLayer, tbl dummy,dummy,zLayer=SidePointToElem(plankInfo.tblSides[4], 0, 0, tranMaster) s=string.format( "PLA%d %s,%s,0,0,%s,%s,180,%s;\r\n", airLayer, Len2Str(dx), Len2Str(dy), Len2Str(x), Len2Str(y), Len2Str(zLayer) ) tbl={} tbl.layerpre=airLayer tbl.cnc=s .. sMarkingCnc return tbl end -- Writes layers starting from coreindex+given dir -- tblelems elemTbl.tblelems -- klayer Core layer -- nDir +-1 -- sInOrOut "A"=outside, "I"=inside function WriteLayers( tranMaster, tblelems, mapElemToPlanks, klayer, nDir, sInOrOut, tblMarkingLayer ) local vlayer, i, sComm, addLayer sComm="EXT" if sInOrOut=="I" then sComm="INT" end i=1 addLayer=0 while true do klayer=klayer+nDir if klayer<1 or klayer>#tblelems then break end if tblMarkingLayer and klayer==tblMarkingLayer.layerpre then -- Insert marking layer here WriteCnc( string.format("TXT --- %s LAYER %d ---;\r\n", sComm, i ) ) WriteCnc( tblMarkingLayer.cnc ) addLayer=1 end vlayer=tblelems[klayer] s=string.format("PL%s%d", sInOrOut, i+addLayer) sPanelLayer=string.format("SL%s%d", sInOrOut, i+addLayer) WriteCnc( string.format("TXT --- %s LAYER %d ---;\r\n", sComm, i+addLayer ) ) WriteElemLayer(tranMaster, vlayer, s, sPanelLayer, mapElemToPlanks, false) i=i+1 end end -- elemTbl from af_request("elem_openparent") plus elemid -- mapElemToPlanks key=elem guid, value=1-based index of planks belonging to the element function SaveMultiLayerElem(fileName, elemTbl, mapElemToPlanks) local elemQuant, hFile, s, sPanelLayer local klayer, vlayer, tblPlanks local globOrig, globx, globy, globz, masterElem, infoMaster, origMaster, tranMaster, plankInfo for klayer,vlayer in ipairs(elemTbl.tblelems) do if not masterElem or vlayer.type=="core" then masterElem=vlayer end end -- Write to file gtblCnc=nil gnCnc=0 gtblPlankEdges=nil -- # Open dest file hFile=io.open(fileName, "wb") if not hFile then error( string.format("ERROR: Opening file %s for writing failed", fileName) ) end io.output( hFile ) -- wup-header... local s local projNum = ac_environment("parsetext", "") --local projName = ac_environment("parsetext", "") --local custName = ac_environment("parsetext", "") s="EXTERNAL" if gbElemLayerInToOut then s="INSIDE" end elemQuant=af_request("elem_quantities", elemTbl.guid, 0) -- WUP wants just core layer size here s=string.format( "VERSION 3.3;\r\n" .. "ANR %s;\r\n" .. "ELB OUTSIDE WALL;\r\n" .. "ELN %s;\r\n" .. "ELA %s;\r\n" .. "ELM %s, %s, %s, 1;\r\n", projNum, elemTbl.elemid, s, Len2Str(elemQuant.geo.x2-elemQuant.geo.x1), Len2Str(elemQuant.geo.y2-elemQuant.geo.y1), Len2Str(elemQuant.geo.z2-elemQuant.geo.z1) ) elemQuant=nil WriteCnc(s) -- globOrig={} globOrig.x=0 globOrig.y=0 globOrig.z=0 globx={} globx.x=1 globx.y=0 globx.z=0 globy={} globy.x=0 globy.y=1 globy.z=0 globz={} globz.x=0 globz.y=0 globz.z=1 infoMaster=af_request("plankinfo", masterElem.guid) origMaster={} origMaster.x=infoMaster.begc.x - infoMaster.width*0.5 * infoMaster.vecy.x origMaster.y=infoMaster.begc.y - infoMaster.width*0.5 * infoMaster.vecy.y origMaster.z=infoMaster.begc.z - infoMaster.width*0.5 * infoMaster.vecy.z if not gbElemLayerInToOut then -- Origin to inner side of the main framing and looking from inside to outside origMaster.x=infoMaster.begc.x + infoMaster.width*0.5 * infoMaster.vecy.x origMaster.y=infoMaster.begc.y + infoMaster.width*0.5 * infoMaster.vecy.y origMaster.z=infoMaster.begc.z + infoMaster.width*0.5 * infoMaster.vecy.z infoMaster.vecy.x=-infoMaster.vecy.x infoMaster.vecy.y=-infoMaster.vecy.y infoMaster.vecy.z=-infoMaster.vecy.z end -- MIRROR X --origMaster.x=origMaster.x + infoMaster.len * infoMaster.vecx.x --origMaster.y=origMaster.y + infoMaster.len * infoMaster.vecx.y --origMaster.z=origMaster.z + infoMaster.len * infoMaster.vecx.z --infoMaster.vecx.x=-infoMaster.vecx.x --infoMaster.vecx.y=-infoMaster.vecx.y --infoMaster.vecx.z=-infoMaster.vecx.z tranMaster=ac_geo("calctranworld3", globOrig, globx, globy, globz, origMaster, infoMaster.vecx, infoMaster.vecz, infoMaster.vecy) -- Core always first local coreInd coreInd=nil for klayer,vlayer in ipairs(elemTbl.tblelems) do if vlayer.type=="core" then coreInd=klayer break end end if not coreInd then error( "No core layer in element - cannot write" ) end WriteElemLayer(tranMaster, elemTbl.tblelems[coreInd], "PLA0", "SLA0", mapElemToPlanks, true) -- Get marking data from airspace and panel layers local tblExtMarkingLayer -- Write per layer local intDir intDir=1 if gbElemLayerInToOut then intDir=-1 end tblExtMarkingLayer=GetExtMarkingCnc( tranMaster, elemTbl.tblelems, mapElemToPlanks, coreInd, -intDir ) if gnWriteLayers==2 then -- also int WriteLayers( tranMaster, elemTbl.tblelems, mapElemToPlanks, coreInd, intDir, "I" ) elseif gnWriteLayers==3 then -- also ext WriteLayers( tranMaster, elemTbl.tblelems, mapElemToPlanks, coreInd, -intDir, "A", tblExtMarkingLayer ) elseif gnWriteLayers==4 then -- also int+ext WriteLayers( tranMaster, elemTbl.tblelems, mapElemToPlanks, coreInd, intDir, "I" ) WriteLayers( tranMaster, elemTbl.tblelems, mapElemToPlanks, coreInd, -intDir, "A", tblExtMarkingLayer ) elseif gnWriteLayers==5 then -- also ext+int WriteLayers( tranMaster, elemTbl.tblelems, mapElemToPlanks, coreInd, -intDir, "A", tblExtMarkingLayer ) WriteLayers( tranMaster, elemTbl.tblelems, mapElemToPlanks, coreInd, intDir, "I" ) end io.close( hFile ) end ----------------------------------------------------------------------------- -- BTL specific constants gnBtlStartEndOffset = 0.020 -- How much waste wood at beg & end of the piece in meters -- Feeding into machine gnBvnPlateSide=EBvnMale gnBvnLogDir=EBvnDirBeg gbBvnCalcFeed=false -- CANNOT ROTATE TO GET GEOMETRY RIGHT: If set to true, plank is fed so that longer extent lies on table (overrides previous settings) gnBvnBestPrintSide=4 -- Depends on the feeding direction: ID and nail marks are placed on this surface gnBvbPolyCutterDia=0.016 -- Diameter of the board cutter tool in the machine gbElemLayerInToOut=false gnWriteLayers=3 -- 1=only core, 2=also int, 3=also ext, 4=also int+ext, 5=also ext+int gnWupIdSide=0 -- 0=no, 1-4=Wup side gnWupIdPos=-0.5 -- Neg=distance from beg, 0=center, pos=dist from end gnWupPanelMarkingsDist=4 -- Every nth panel gets marked gnWupExpandSel=1 -- Extend selection to contain everything in the element 0/1 gnWupOpeningBotTop=0 -- Customer specific cuts at top of windows (0=none) gnWupSawExtraDepth=0.001 -- How much deeper than the layer the saw cuts are written gnWupSawExtraDepthBoard=0.003 -- How much deeper than the layer the saw cuts are written for boards