-- 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 EMcVCutBeg = 114 EMcFrAngledEndOld = 200 EMcFrAngledEnd = 201 EMcFrAngledEndTenon = 210 EMcFrEndHiddenShoe = 211 EMcVCutEnd = 214 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 EMcFrBeamFemale = 901 -- Ostlaft tak�s balk joint in log G2 etc EMcFrBalkShoe = 1000 gsWupNailGun=nil -- WupWriteElemLayer sets if set in the element information --------------------------------------------------------------------------- -- ARCHIFRAME & ARCHILOGS COMMON FUNCTIONS function toLog(str) ac_environment("tolog", str) end -- Dumps given variable into string function DumpTblInt(o, nIndent) local s,k,v if type(o) == 'table' then s = '{\n' for i = 0, nIndent do s = s .. '\t' end for k,v in pairs(o) do if type(k) ~= 'number' then k = '"'..k..'"' end s = s .. '['..k..'] = ' .. DumpTblInt(v, nIndent + 1) .. ',\n' for i = 0, nIndent do s = s .. '\t' end end if string.len(s) > 1 then if string.sub(s, -1) == '\t' then s = string.sub(s, 1, -2) end end s=s .. '}' else s = "" for i = 0, nIndent do s = s .. '\t' end if type(o) == 'string' then s = '"' .. tostring(o) .. '"' elseif type(o) == 'number' then -- Less digits to be able to compare results s = '"' .. string.format("%0.4f", o) .. '"' else s = tostring(o) end end return s end -- Dumps a table to the log window with indentation. function DumpTbl(o) ac_environment("tolog", DumpTblInt(o, 0)) 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 function GetSgn(d) if d<-EPS then return -1 end if d>EPS then return 1 end return 0 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 function UnifyBoundbox(x, y, x1, y1, x2, y2) if xx2 then x2=x end if y>y2 then y2=y end return x1, y1, x2, y2 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, bDontCheckIds ) if sId=="" then local guid guid=ac_objectget("#guid") if guid==nil then guid="" else guid=" GUID=" .. guid end AddCncErr( 0, string.format("EMPTY ID FOUND" .. guid) ) elseif gtblUsedIds[sId]~=null and not bDontCheckIds then AddCncErr( 0, string.format("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 -- Returns cnc_elemtype for the element: Either from composite's cnc_elemtype or from related cutlists, nil=not found function GetCncElemType(elemQuant) local cnc_elemtype if elemQuant.xmlutf8 then cnc_elemtype=string.match(elemQuant.xmlutf8, " cnc_elemtype=\"(.-)\"") end if elemQuant.tblguidcutlist then -- Check if there is info cnc_elemtype in the cut list(s) local i, v, i2, parName, found, par, eleminfo found=nil for i,v in ipairs(elemQuant.tblguidcutlist) do -- Check if cut list is text only eleminfo=ac_elemget(v) if eleminfo and eleminfo.header and eleminfo.header.typeID==6 then ac_objectopen(v) for i2=1,10 do parName=string.format("iInfoKey%d", i2) par=ac_objectget(parName) if par and par=="cnc_elemtype" then parName=string.format("iInfoValue%d", i2) par=ac_objectget(parName) if par and par~="" then found=par end end end ac_objectclose() if found then cnc_elemtype=found break end end end end return cnc_elemtype end -- Empty ID may affect wup writing function WupGetPartId(id) if not id then id=ac_objectget("#id") end if not id or id=="" then id="_" end return id 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 function CaclDot3D(v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end function CaclDot2D(v1, v2) return v1.x * v2.x + v1.y * v2.y 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 -- ### Sector and plane intersection -- in: -- ta,tb,tc,td Plane equation, a-c=normal, d=distance from zero -- x1, y1, z1 sector -- x2, y2, z2 sector -- out: -- hasX Was there an intersection -- x, y, z Point -- dist Distance from point x1,y1,z1 in line's dir (relative to the sector's length 0...1!) 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 -- To return correct dist: return true, x, y, z, dist*math.abs(denom) 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)0.995 then if math.abs(val)<0.049 then s="0" -- Avoid "-0" else s=string.format("%.0f", val) end else s=string.format("%.02f", val) end if string.len(string.format("%.0f", val))>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 -- No decimals function WupLen2StrNoDec(val) local s, off val=val*1000 off=math.abs(val-math.floor(val)) if math.abs(val)<0.049 then s="0" -- Avoid "-0" else s=string.format("%.0f", val) end if string.len(string.format("%.0f", val))>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 -- Single decimal since ArchiFrame adjusts spacing to have single decimal function WupLen2StrNail(val) local s, off val=val*1000 off=math.abs(val-math.floor(val)) if math.abs(val)<0.049 then s="0" -- Avoid "-0" else s=string.format("%.1f", val) end if string.len(string.format("%.1f", val))>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 wup-length str (max 8 digits for now) function WupInt2Str(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 -- Adds cnc position and content to table. Table will be sorted at the end. function WupAddMc(nPos, sText) local i if nPos==nil then error("WupAddMc/nPos") end if sText==nil then error("WupAddMc/sText") end if gnCncCount==0 then gTblCnc={} end gnCncCount=gnCncCount+1 gTblCnc[gnCncCount] = {} gTblCnc[gnCncCount].pos =nPos gTblCnc[gnCncCount].count =gnCncCount gTblCnc[gnCncCount].text =sText end -- Adds one groove in Logs coordinates to bvn-file. Adds notice if not possible. -- sWupCode FRZ/FRY -- nDepthMul 1/-1 -- groFace: 1=top, 2=front, 3=bottom, 4=back, 5=beg, 6=end -- recCount is for internal use -- Coordinates are always to whole log! function WupAddGro(sWupCode, nDepthMul, bDepthToY, sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen, recCount) local nBtlSide, bMirrorY local nAngle, nGroLen, dx, dy, faceHeight, s faceHeight=gnProfHeight if groFace==1 or groFace==3 then faceHeight=gnCurrWidth end if x1<-1 or x1>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 gnWupWarnSkipMc>0 then AddCncErr( x1, "ERROR: WUP DOES NOT SUPPORT GROOVE AT THE END OF THE PIECE, PLEASE FORCE THE GROOVE TO ONE OF THE SIDES." ) end 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 if gnWupWarnSkipMc>0 then AddCncErr( x1, "ERROR: WUP SUPPORTS ONLY STRAIGHT GROOVES X1=X2 WITHOUT TILT/ROTATION ANGLE, GROOVE SKIPPED" ) end return end if y1>y2 then if gnWupWarnSkipMc>0 then AddCncErr( x1, "ERROR: WUP GROOVE Y1>Y2, GROOVE SKIPPED" ) end 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, WupLen2Str(x1-groWidth), WupLen2Str(groWidth), WupLen2Str(ty), WupLen2Str(tz)) WupAddMc( x1-groWidth, s ) end gsWupChars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-/" function WupToWupStr(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 function WupStrRemoveComma(s) local res, len, i1, c len=string.len(s) res="" for i1=1,len do c=string.sub(s,i1,i1) if s~="," then res=res .. c end end return res end -- Returns material index value as string function WupGetMatIndex(sDefault) local res res=ac_objectget("cnc_matindex") if res and res~="" then return WupStrRemoveComma(res) end res=gsWupMatIndex -- Never nil if res=="" and sDefault then res=sDefault end return res end -- isBoard true=current item is a board (nil=not) function WupGetMatName(sId, isBoard) local name sId=WupToWupStr(sId) name=ac_objectget("cnc_matname") if not name or name=="" then if isBoard==nil then isBoard=false end if gbWupMatNameForBoard==isBoard then name=gsWupMatName end end if name and name~="" and name~=" " then sId=WupStrRemoveComma(name) .. " " .. sId end return sId 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 WupGetEndPoints(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=WupSidePointToElem(currSide, pointsBeg[i].x, pointsBeg[i].y, tranElem) s=string.format("%s%s,%s, ", s, WupLen2Str(x), WupLen2Str(y)) i=i-1 end for i=1,nEnd do x,y,z=WupSidePointToElem(currSide, pointsEnd[i].x, pointsEnd[i].y, tranElem) s=string.format("%s%s,%s, ", s, WupLen2Str(x), WupLen2Str(y)) end else -- Collect cw for i=1,nBeg do x,y,z=WupSidePointToElem(currSide, pointsBeg[i].x, pointsBeg[i].y, tranElem) s=string.format("%s%s,%s, ", s, WupLen2Str(x), WupLen2Str(y)) end i=nEnd while i>0 do x,y,z=WupSidePointToElem(currSide, pointsEnd[i].x, pointsEnd[i].y, tranElem) s=string.format("%s%s,%s, ", s, WupLen2Str(x), WupLen2Str(y)) i=i-1 end end return s end function WupCncTblToStr() 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 -- Selects the surface having smaller z-value (closer to the table) -- Returns three values: -- First closer of side1 or side2 -- Second Smaller zoff value -- Third Bigger zoff value function WupFindMinZSide(plankInfo, tranElem, side1, side2) local d, zoff1, zoff2 d,d,zoff1=WupSidePointToElem(plankInfo.tblSides[side1], 0, 0, tranElem) d,d,zoff2=WupSidePointToElem(plankInfo.tblSides[side2], 0, 0, tranElem) if zoff2gnCurrTotLen-0.001 and deptha>0 then -- Handle just full length cuts and forget about the angles if side==sidetop and deptha>topoff then -- Cut from top topoff=deptha elseif side==sidebot and deptha>bottomoff then bottomoff=deptha end end end end height=height-topoff-bottomoff if height<0 then height=0.001 end return bottomoff, topoff, height end -- Calculates y-coordinate for a surface for a plank's machining -- faceFront Same as cutLineSide, the side towards wup camera 1-4 -- Returns: edited yc, boolean is mirrored local function WupGetMcY(faceFront, currFace, plankinfo, y) local mirror=false if faceFront==3 or faceFront==4 then -- Need to mirror, note that watching direction was swapped from element direction at the beginning if currFace<5 then local dy=plankinfo.height if currFace==1 or currFace==3 then dy=plankinfo.width end y=dy-y mirror=true end end return y, mirror end function WupWritePlankInt(unidPlank, plankInfo, tranElem, elemlayer, sPlaPlank, tblDrills) local s, n, i, nOther, x, y, z, nDepth, x1, x2, dx, dy, nWidth, sBin, sPart, sMat, angle 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 local bEbt local b2ndFraming -- Framing codes except OG/UG (well, let those come anyway) b2ndFraming=false if sPlaPlank and sPlaPlank=="" then b2ndFraming=true sPlaPlank=nil end -- Is it excluded from wup-file bEbt=ac_objectget("iCncFraming") if bEbt and bEbt==-1 then return nil -- Added 7/2023: Skip if cnc framing is minus one end if bEbt~=nil and bEbt==0 then bEbt=true else bEbt=false end if bEbt and sPlaPlank then -- Not core - just discard the element -- let insulation layer planks come --return nil sPlaPlank=nil end gbSkipCncErr=false if sPlaPlank then gbSkipCncErr=true end BvnGetPlankBasicParams(unidPlank) if not sPlaPlank and not gtblPlankEdges then CheckId(gsCurrentLogId, true) end by=gnCurrWidth hz=gnProfHeight local cutoffbottom=0 -- Lengthwise cut may change local cutofftop=0 -- Plank's z-vectors dir in element's coordinates x,y,z=MatTransformVec(plankInfo.vecz.x, plankInfo.vecz.y, plankInfo.vecz.z, tranElem) if z>0.99 or z<-0.99 then --if math.abs(elemData.rotangle)>1 and math.abs(elemData.rotangle-180)>1 and math.abs(elemData.rotangle+180)>1 then -- error("elemdata 0/180") --end cutLineSide=WupFindMinZSide(plankInfo, tranElem, 1, 3) elseif math.abs(z)<0.01 then --if math.abs(elemData.rotangle-90)>1 and math.abs(elemData.rotangle-270)>1 and math.abs(elemData.rotangle+90)>1 then -- error("elemdata 90/270") --end cutLineSide=WupFindMinZSide(plankInfo, tranElem, 2, 4) hz=gnCurrWidth by=gnProfHeight -- Check if (cladding) plank has lengthwise cut cutoffbottom,cutofftop,by=FindLengthwiseCut(by, 1, 3) else AddCncErr( 0, string.format("Unsupported z-vector %f - plank skipped", z) ) 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 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) --toLog(string.format("%s\nxx=%f xy=%f xz=%f", ac_objectget("#id"), xx, xy, xz)) --toLog(string.format("yx=%f yy=%f yz=%f", yx, yy, yz)) ydir=ac_geo("linedist", 0, 0, xx, xy, yx, yy) -- Negative=y-axis to left, positive=y-axis to right --toLog(string.format("by=%f cutofftop=%f cutoffbottom=%f ydir=%f", by, cutofftop, cutoffbottom, ydir)) if ydir<0 then wupx1,wupy1,zoff=WupSidePointToElem(currSide, 0, by+cutofftop, tranElem) else wupx1,wupy1,zoff=WupSidePointToElem(currSide, 0, cutoffbottom, tranElem) end -- wupx1, wupy1 is the lower left of local surface, not in target elem's coords --toLog(string.format("wupx1=%f wupy1=%f", wupx1, wupy1)) if gtblPlankEdges then local tblNew tblNew={} tblNew.id=gsCurrentLogId tblNew.ptr=plankInfo.ptr tblNew.right={} tblNew.right.x1, tblNew.right.y1=WupSidePointToElem(currSide, 0, cutoffbottom, tranElem) tblNew.right.x2, tblNew.right.y2=WupSidePointToElem(currSide, plankInfo.len, 0, tranElem) tblNew.left={} tblNew.left.x1, tblNew.left.y1=WupSidePointToElem(currSide, 0, cutoffbottom+by, tranElem) tblNew.left.x2, tblNew.left.y2=WupSidePointToElem(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 isBeg=(nType==EMcFrAngledBeg) -- Check if full plank cut nAngleTop=ac_objectget("iMc", i, 2) -- In degrees nAngleSide=ac_objectget("iMc", i, 3) -- Bevel (Tilt angle in mc dlg) if ((cutLineSide==1 or cutLineSide==3) and math.abs(nAngleTop-90)>1) or ((cutLineSide==2 or cutLineSide==4) and math.abs(nAngleSide-90)>1) then local tblInfo, tblRes, tblLine tblInfo={} tblInfo.guid = unidPlank tblInfo.mcindex = i tblInfo.cutlineside=cutLineSide tblRes = af_request("mc_getcutinfo", tblInfo) --toLog(string.format("cutLineSide=%d, tblRes.x1=%f tblRes.y1=%f tblRes.x2=%f tblRes.y2=%f", cutLineSide, tblRes.x1, tblRes.y1, tblRes.x2, tblRes.y2)) if math.abs(tblRes.x2-tblRes.x1)+math.abs(tblRes.y2-tblRes.y1)>0.001 then 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 end elseif gnWupWarnSkipMc>0 then AddCncErr( 0, "ERROR: UNSUPPORTED ANGLED CUT SKIPPED, isBeg=%s", tostring(isBeg) ) 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=WupSidePointToElem(currSide, 0, cutoffbottom+by*0.5, tranElem) midx2,midy2=WupSidePointToElem(currSide, gnCurrTotLen, cutoffbottom+by*0.5, tranElem) x,y=WupSidePointToElem(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 if not sPlaPlank then y1=WupGetMcY(cutLineSide, groFace, plankInfo, y1) y2=WupGetMcY(cutLineSide, groFace, plankInfo, y2) if y1>y2 then -- WupAddGro requires x1,y1,x2,y2=x2,y2,x1,y1 end WupAddGro(s, mul, bDepthToY, sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) end elseif nType==EMcFrDrill then if sPlaPlank then -- In element coordinates (cladding piece for example) if tblDrills then WupAddDrillToTbl(tranElem, plankInfo, i, tblDrills) else WupShowErrMsg( string.format("WARNING: DRILL IN %s id=[%s] AND NO DRILLING TABLE - DRILLING SKIPPED", sPlaPlank, ac_objectget("#id")) ) end else -- These are in plank local coordinates local mul 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) mul=1 if groFace==cutLineSide then s="BOZ" elseif groFace==1+((cutLineSide - 1 + 2) % 4) then -- Back side s="BOZ" mul=-1 else -- Calculate if on the left or right side of the plank local midx1, midy1, midx2, midy2 midx1,midy1=WupSidePointToElem(currSide, 0, cutoffbottom+by*0.5, tranElem) midx2,midy2=WupSidePointToElem(currSide, gnCurrTotLen, cutoffbottom+by*0.5, tranElem) x,y=WupSidePointToElem(plankInfo.tblSides[groFace], 0, 0, tranElem) s="BOY" if ac_geo("linedist", x, y, midx1, midy1, midx2, midy2)<0 then mul=-1 end end y1=WupGetMcY(cutLineSide, groFace, plankInfo, y1) -- Inverse always if groFace==1 or groFace==3 then y1=gnCurrWidth-y1 else y1=gnProfHeight-y1 end if groFace>4 then if gnWupWarnSkipMc>0 then AddCncErr( 0, "ERROR: DRILLING AT THE BEG/END OF PIECE SKIPPED" ) end 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, WupLen2Str(x1), WupLen2Str(y1), WupLen2Str(groWidth), WupLen2Str(mul*groDepth)) WupAddMc( x1, s ) end 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 if gnWupWarnSkipMc>0 then AddCncErr( 0, "ERROR: UNSUPPORTED ANGLED CUT AT SKIPPED, isBeg=%s", tostring(isBeg) ) end 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 sPlaPlank 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 sPlaPlank 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=cutoffbottom pointsBeg[2]={} pointsBeg[2].x=0 pointsBeg[2].y=cutoffbottom+by -- by is total height-cutoffbottom-cutofftop nBeg=2 end if nEnd==0 then pointsEnd={} pointsEnd[1]={} pointsEnd[1].x=gnCurrTotLen pointsEnd[1].y=cutoffbottom pointsEnd[2]={} pointsEnd[2].x=gnCurrTotLen pointsEnd[2].y=cutoffbottom+by nEnd=2 end end local sId sId=gsCurrentLogId if sId=="" then sId="(ID)" end -- Precalculate rectangle coords used in PLx, EBT and others. rdx is nil if not vertical/horizontal local rdx, rdy, rx1, ry1, elemGroup elemGroup=ac_objectget("iElemGroup") if math.abs(xy)>0.999 then rdx=by rdy=gnCurrTotLen elseif math.abs(xx)>0.999 then rdx=gnCurrTotLen rdy=by end if rdx then local x1, y1 -- Find minimum x1,y1 rx1,ry1=WupSidePointToElem(currSide, 0, cutoffbottom, tranElem) x1,y1=WupSidePointToElem(currSide, currSide.dx, cutoffbottom, tranElem) if x1+EPS3 and string.sub(sPlaPlank, 1, 2)=="PL" then writeBt6 = "SL" .. string.sub(sPlaPlank, 3, -1) sPlaPlank=nil end end --toLog(string.format("2: sPlaPlank=%s, nBeg=%d, nEnd=%d", tostring(sPlaPlank), nBeg, nEnd)) if sPlaPlank then -- PLA for plank, just rectangle if not rdx then -- Not supported by wup and must be called from markings return nil end comp=sPlaPlank s=string.format( "%s %s, %s, %s, %s, %s, %s, %s, %s", sPlaPlank, WupLen2Str(rdx), WupLen2Str(rdy), WupLen2Str(hz), WupLen2Str(rx1), WupLen2Str(ry1), WupGetMatIndex("1"), WupGetMatName(sId), WupLen2Str(zoff) ) elseif writeBt6 or (not bEbt and (nBeg>2 or nEnd>2)) 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 writeBt6 then comp=writeBt6 end s=string.format( "%s %s, %s, %s, %s%s, %s, %s", comp, WupLen2Str(gnCurrTotLen), WupLen2Str(by), WupLen2Str(hz), WupGetBtPoints(currSide, pointsBeg, pointsEnd, tranElem), WupGetMatIndex("1"), WupGetMatName(sId), WupLen2Str(zoff) ) elseif not bEbt and (nBeg==2 and nEnd==2) then comp="BT4" s=string.format( "BT4 %s, %s, %s, %s%s, %s, %s", WupLen2Str(gnCurrTotLen), WupLen2Str(by), WupLen2Str(hz), WupGetBtPoints(currSide, pointsBeg, pointsEnd, tranElem), WupGetMatIndex("1"), WupGetMatName(sId), WupLen2Str(zoff) ) else if not bEbt and (nBeg>0 or nEnd>0) then AddCncErr( 0, "WARNING: Unknown end types - written as straight" ); end -- Select the type based on orientation and usage if math.abs(xy)>math.abs(xx) then -- Vertical comp="QS" else local checkDir, orgguid -- Actually here we need to write lower left in elem coordinates wupx1=rx1 wupy1=ry1 checkDir=true orgguid=plankInfo.orgguid -- If swapped dir if not orgguid then orgguid=plankInfo.guid end if (elemlayer.tblOgGuids and elemlayer.tblOgGuids[orgguid]) or (not elemlayer.tblOgGuids and string.match(elemGroup, "^top_%a*")) then comp="OG" sortstr="002" wasOgUg=true elseif (elemlayer.tblUgGuids and elemlayer.tblUgGuids[orgguid]) or (not elemlayer.tblUgGuids and string.match(elemGroup, "^bottom_%a*")) then comp="UG" sortstr="003" wasOgUg=true end if not wasOgUg then comp="LS" checkDir=false end end if bEbt then -- This is just a rectangle if not rdx then AddCncErr(0, "WARNING: Only vertical or horizontal EBTs are written, part skipped from wup") return nil end comp="EBT" s=string.format( "%s %s, %s, %s, %s, %s, %s, %s, %s", comp, WupLen2Str(rdx), WupLen2Str(rdy), WupLen2Str(hz), WupLen2Str(rx1), WupLen2Str(ry1), WupGetMatIndex("1"), WupGetMatName(sId), WupLen2Str(zoff) ) else s=string.format( "%s %s, %s, %s, %s, %s, %s, %s, %s", comp, WupLen2Str(gnCurrTotLen), WupLen2Str(by), WupLen2Str(hz), WupLen2Str(wupx1), WupLen2Str(wupy1), WupGetMatIndex("1"), WupGetMatName(sId), WupLen2Str(zoff) ) end end -- Top&bottom plates must be in element's direction: Actually never reached since swapped is saved as BT4 if not bEbt and (string.match(elemGroup, "^top_%a*") or string.match(elemGroup, "^bottom_%a*")) and not wasOgUg and CaclDot3D(plankInfo.vecx, g_infoMaster.vecx)<0 then if not gbSkipCncErr then AddCncErr(0, string.format("ERROR: TOP/BOTTOM PLATE DIRECTION INCOMPATIBLE FOR OG/UG (NOW SAVED AS %s) - PLEASE SWAP DIRECTION OF %s", comp, elemGroup) ) end end local tblRes={} local s2 s2=unidPlank if gtblPlankGuid2GroupGuid then s2=gtblPlankGuid2GroupGuid[unidPlank] end s = s .. ";\r\n" --if s2 then -- s = s .. string.format("PROPERTY BTLGUID,\"%s\";\r\n", s2) --end s = s .. WupCncTblToStr() tblRes.cnc=s tblRes.guid=unidPlank -- Called with guid tblRes.comp=comp tblRes.x1=wupx1 tblRes.y1=wupy1 tblRes.vecx={} tblRes.vecx.x=xx tblRes.vecx.y=xy tblRes.vecx.z=xz s=ac_objectget("iElemModule") if s==nil then s="" -- Old object end if s~="" and elemlayer.type~="core" then -- Add layer type to the module name s=s .. string.format("-%s", elemlayer.type) end tblRes.module=s tblRes.plankinfo=plankInfo tblRes.plaplank=sPlaPlank return tblRes 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", "") gnCncErrCount=0 gtblUsedIds = {} gnBoardPieceNum=500000 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 =gnWupElemLayerInToOutDef tblSettings[4] ={} tblSettings[4].cfgonly =0 tblSettings[4].type =1 tblSettings[4].prompt ="Write layers" tblSettings[4].key ="wuplayers" tblSettings[4].defvalue =gnWupWriteLayersDef tblSettings[4].valuelist="\"1:Only core\",\"2:Core+interior\",\"3:Core+exterior\",\"4:Core+interior+exterior\"" tblSettings[5] ={} tblSettings[5].cfgonly =1 tblSettings[5].type =1 tblSettings[5].prompt ="Extend selection" tblSettings[5].key ="wupextsel" tblSettings[5].defvalue =1 tblSettings[5].valuelist="\"1:No - write just selected pieces\",\"2:Write all layers & pieces related to selection\",\"3:Write all layers & pieces related to selection NOT including hidden layers\"" tblSettings[6] ={} tblSettings[6].cfgonly =0 tblSettings[6].type =1 tblSettings[6].prompt ="Cuts at openings' top & bottom" tblSettings[6].key ="wupopeningtopbot" tblSettings[6].defvalue =gnWupOpeningBotTop+1 tblSettings[6].valuelist="\"1:No\",\"2:Win top&bottom, door tops for cladding\"" tblSettings[7] ={} tblSettings[7].cfgonly =1 tblSettings[7].type =3 tblSettings[7].prompt ="Sawing extra depth to layer thickness boards" tblSettings[7].key ="wupsawextraboard" tblSettings[7].defvalue =gnWupSawExtraDepthBoard tblSettings[8] ={} tblSettings[8].cfgonly =1 tblSettings[8].type =3 tblSettings[8].prompt ="Sawing extra depth to layer thickness others" tblSettings[8].key ="wupsawextra" tblSettings[8].defvalue =gnWupSawExtraDepth tblSettings[9] ={} tblSettings[9].cfgonly =1 tblSettings[9].type =3 tblSettings[9].prompt ="Shorten undercuts in panel layer" tblSettings[9].key ="wupshortenpanelcut" tblSettings[9].defvalue =gnWupShortenUndercut tblSettings[10] ={} tblSettings[10].cfgonly =1 tblSettings[10].type =2 tblSettings[10].prompt ="Join single nailings to lines if possible" tblSettings[10].key ="wupsjoinnails" tblSettings[10].defvalue =0 tblSettings[11] ={} tblSettings[11].cfgonly =1 tblSettings[11].type =1 tblSettings[11].prompt ="Nailings ordering" tblSettings[11].key ="wupsortnails" tblSettings[11].defvalue =gnWupSortNailings tblSettings[11].valuelist="\"1:Minimize nail gun movements (please see help Alt+Shift+F1)\",\"2:In planks' order\"" tblSettings[12] ={} tblSettings[12].cfgonly =0 tblSettings[12].type =2 tblSettings[12].prompt ="Default saw cuts if no ArchiFrameSawCut-objects" tblSettings[12].key ="wupsawcuts" tblSettings[12].defvalue =1 tblSettings[13] ={} tblSettings[13].cfgonly =0 tblSettings[13].type =1 tblSettings[13].prompt ="Write markings for ext studding and cladding" tblSettings[13].key ="wupmarkings" tblSettings[13].defvalue =gnWupPlaMarkings+1 tblSettings[13].valuelist="\"1:No markings\",\"2:Write additional layer PLA2 having all markings\",\"3:Write additional layer just before the layer to be marked\",\"4:Write additional layer PLA2 having all markings excluding cladding\"" tblSettings[14] ={} tblSettings[14].cfgonly =1 tblSettings[14].type =1 tblSettings[14].prompt ="Rotation" tblSettings[14].key ="wuprotate" tblSettings[14].defvalue =1 tblSettings[14].valuelist="\"1:No rotation\",\"2:Rotate 90 deg clockwise\",\"3:Rotate 90 deg counter clockwise\",\"4:Rotate 180 deg\",\"5:Mirror Y\",\"6:Mirror Y and swap watching direction\"" tblSettings[15] ={} tblSettings[15].cfgonly =1 tblSettings[15].type =1 tblSettings[15].prompt ="Plank directions etc" tblSettings[15].key ="wupgeo" tblSettings[15].defvalue =1 tblSettings[15].valuelist="\"1:No changes\",\"2:Change BT4-pieces to LS/QS/OG/UG by swapping direction and set OG/UG based on part's position\"" tblSettings[16] ={} tblSettings[16].cfgonly =1 tblSettings[16].type =1 tblSettings[16].prompt ="Value of ELA" tblSettings[16].key ="wupela" tblSettings[16].defvalue =1 tblSettings[16].valuelist="\"1:Automatic\",\"2:ELA INTERIOR;\",\"3:ELA EXTERIOR;\"" tblSettings[17] ={} tblSettings[17].cfgonly =1 tblSettings[17].type =4 tblSettings[17].prompt ="Tool number for markings" tblSettings[17].key ="wuptoolmark" tblSettings[17].defvalue =tostring(gnWupMarkTool) tblSettings[18] ={} tblSettings[18].cfgonly =1 tblSettings[18].type =1 tblSettings[18].prompt ="Cut if no ArchiFrameSawCuts, PSG=saw, PAF=router" tblSettings[18].key ="wuppsgpaf" tblSettings[18].defvalue =1 tblSettings[18].valuelist="\"1:PSG\",\"2:PAF\"" tblSettings[19] ={} tblSettings[19].cfgonly =1 tblSettings[19].type =4 tblSettings[19].prompt ="No glue AC hatch layer, empty=skip" tblSettings[19].key ="wupnoglue" tblSettings[19].defvalue ="" tblSettings[20] ={} tblSettings[20].cfgonly =1 tblSettings[20].type =3 tblSettings[20].prompt ="Combine marking lines max dist (<=-1 m=no)" tblSettings[20].key ="wupcombmax" tblSettings[20].defvalue = -1 tblSettings[21] ={} tblSettings[21].cfgonly =0 tblSettings[21].type =2 tblSettings[21].prompt ="Warn about skipped machinings" tblSettings[21].key ="wupwarnskip" tblSettings[21].defvalue =0 tblSettings[22] ={} tblSettings[22].cfgonly =0 tblSettings[22].type =2 tblSettings[22].prompt ="Pair orphan saw cuts with closest projection" tblSettings[22].key ="wuporphansaw" tblSettings[22].defvalue =0 gHelpAnchor="afdlgwupexport" 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 gbWupElemLayerInToOut=false if tblSettings[3].value~=0 then gbWupElemLayerInToOut=true end gnWupWriteLayers =tblSettings[4].value gnWupExpandSel =tblSettings[5].value-1 gnWupOpeningBotTop =tblSettings[6].value-1 gnWupSawExtraDepthBoard=tblSettings[7].value gnWupSawExtraDepth =tblSettings[8].value gnWupShortenUndercut=tblSettings[9].value gbWupJoinNailings=false if tblSettings[10].value==1 then gbWupJoinNailings=true end gnWupSortNailings=tblSettings[11].value gbWupDefSawCuts=false if tblSettings[12].value==1 then gbWupDefSawCuts=true end gnWupPlaMarkings=tblSettings[13].value-1 gnWupRotate=0 -- In radians, positive=clockwise if tblSettings[14].value==2 then gnWupRotate=PI2 elseif tblSettings[14].value==3 then gnWupRotate=-PI2 elseif tblSettings[14].value==4 then gnWupRotate=PI end gnWupMirrorY=0 -- 0=no, 1=yes, 2=yes and swap watching dir if tblSettings[14].value==5 then gnWupMirrorY=1 elseif tblSettings[14].value==6 then gnWupMirrorY=2 end gnWupGeoChange=0 if tblSettings[15].value==2 then gnWupGeoChange=1 end gnWupEla=tblSettings[16].value gnWupMarkTool=tonumber(tblSettings[17].value) if not gnWupMarkTool then gnWupMarkTool=1122 end gsWupDefaultCut="PSG" if tblSettings[18].value==2 then gsWupDefaultCut="PAF" end gsWupNoGlueLayer=tblSettings[19].value gnWupCombineMarkingMaxDist=tblSettings[20].value gnWupWarnSkipMc=tblSettings[21].value -- 0=no, 1=yes gnWupPairOrphanSaw=tblSettings[22].value -- 0=no, 1=yes 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 WupFindElemsFromPlanks(tblPlanks) local i,v local tblGuids, tblInfo, tbl tblGuids={} 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 WupGroupElements(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 local idest, add tblElemPlanks={} idest=0 for i3,v3 in ipairs(tbl) do add=true if nExtendSel==2 then add=ac_environment ("filterelem", v3.guid, 2) end if add then idest=idest+1 tblElemPlanks[idest]=v3.guid end 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 WupGetElemFileName(strFileName, strElemId) local path, fname, ext path, fname, ext=SplitFileName(strFileName, strNum) -- Module name and file name may be empty (or save as dialog actually requires a character, consider "_" to be empty) if fname=="-" then fname="" end if fname~="" then fname = fname .."_" end return string.format("%s%s%s%s", path, fname, strElemId, 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 gnUid=1 gnWarnings=0 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=WupFindElemsFromPlanks(gTblPlanks) tblElemGroups,mapElemToPlanks=WupGroupElements(mapElemToPlanks, gnWupExpandSel) -- Still having only planks, no boards yet -- Process by multilayer element group local i1, v1, fileName, elemTbl gsWupSettingsChanges="" for i1,v1 in pairs(tblElemGroups) do fileName=WupGetElemFileName(strFileName, v1.elemid) elemTbl=af_request("elem_openparent", v1.guid) WupSaveMultiLayerElem(fileName, v1, mapElemToPlanks) af_request("elem_closeparent", 0) end if gsWupSettingsChanges~="" then -- 10/2024: This is normal operation - no reason to info --toLog("NOTE - THESE SETTINGS WERE OVERRIDDEN PER ELEMENTS FROM cnc_wupsettings:\n") --toLog(gsWupSettingsChanges) gsWupSettingsChanges=nil end end -- Adds single drilling to the drilling table -- mcindex Machining index -- infoOrig "plankinfo" for the board/plank -- tblDrills Drills in parent element coordinates, fields x,y,dia,depth,vecz. nil=do not save drills (for planks) function WupAddDrillToTbl(tranMaster, infoOrig, mcindex, tblDrills) local dia, depth, side, x2, y2, z2 x =ac_objectget("iMc", mcindex, 2) y =ac_objectget("iMc", mcindex, 3) dia =ac_objectget("iMc", mcindex, 4) depth =ac_objectget("iMc", mcindex, 5) side =ac_objectget("iMc", mcindex, 6) currSide=infoOrig.tblSides[side] x2,y2,z=MatTransformVec( currSide.vecz.x, currSide.vecz.y, currSide.vecz.z, tranMaster ) if math.abs(z)<0.999 then WupShowErrMsg( string.format("WARNING: DRILL IN [%s] IN BAD SIDE %d - SKIPPED", ac_objectget("#id"), side) ) else -- Transform to the layer (forget z) x, y=WupSidePointToElem(currSide, x, y, tranMaster) local scan, found found=false for scan=1,#tblDrills do if math.abs(tblDrills[scan].x - x)0 and (depth==0.0 or depth>tblDrills[scan].depth) then tblDrills[scan].depth=depth end break end end if not found then -- Collect to table in case there is a drilling between boards local tbl tbl={} tbl.x=x tbl.y=y tbl.dia=dia tbl.depth=depth tbl.vecz=z tblDrills[#tblDrills+1]=tbl end end end -- Gives cnc for drills and nails and saw lines -- infoOrig "plankinfo" for the board/plank -- tblDrills Drills in parent element coordinates, fields x,y,dia,depth,vecz. nil=do not save drills (for planks) -- 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 -- tblNails Nailings, nil=create here for current item. Fields: x1, y1, x2, y2, spacing, nailindex (string), point (set only if item is a point: x1=x2, y1=y2. Otherwise nil) -- sNailIndex NR index/nail device -- Returns cnc-text only if using point nailings, also board markings added function WupGetNailDrillCnc(guidCurr, tranMaster, infoOrig, tblDrills, tblSawCuts, bNailPoints, tblNails, sNailIndex, zLayer, tblMarkings) local n, i, nType, hasNail, sCnc, s, x, y, z, currSide, nTblNails, nTblMarkings local x1, y1, x2, y2, groFace, currId nTblNails=#tblNails nTblMarkings=#tblMarkings sCnc="" n=ac_objectget("iMc" ,-1) currId=WupGetPartId() 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==EMcFrMarking then -- Only board markings if infoOrig.type==4 then local xPosText, yPosText, fontSize, textOff, nParallel, bLine 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 local line = {} line.p0 = {} line.p1 = {} local x1, y1, z1 = WupSidePointToElem(infoOrig.tblSides[groFace], x1, y1, tranMaster, 0) local x2, y2, z2 = WupSidePointToElem(infoOrig.tblSides[groFace], x2, y2, tranMaster, 0) line.p0.x = x1 line.p0.y = y1 line.p0.z = z1 line.p1.x = x2 line.p1.y = y2 line.p1.z = z2 nTblMarkings=nTblMarkings+1 tblMarkings[nTblMarkings]=line end elseif nType==EMcFrNailLine then -- Mc data local x1, y1, x2, y2, z2, cols, rows, anglexRad, angleyRad, spacing, side, len, nails, dx, dy, flags, sNailIndexNow 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) sNailIndexNow=sNailIndex dx = ac_objectget("iMc", i, 9) if dx~=0 then sNailIndexNow=string.format("%d", dx) end 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 = WupSidePointToElem(currSide, xc, yc, tranMaster) -- NR x1,y1,x2,y2,a,i; s = string.format( "NR %s, %s, %s, %s, 1, %s;\r\n", WupLen2StrNail(x), WupLen2StrNail(y), WupLen2StrNail(x), WupLen2StrNail(y), sNailIndexNow ) sCnc = sCnc .. s end else -- Nail lines, save to table local item x1,y1 = WupSidePointToElem(currSide, x1, y1, tranMaster) x2,y2 = WupSidePointToElem(currSide, x2, y2, tranMaster) item={} item.guid=guidCurr item.x1=x1 item.y1=y1 item.x2=x2 item.y2=y2 item.spacing=spacing item.nailindex=sNailIndexNow item.id=currId if math.abs(x1-x2)0.010 then WupShowErrMsg( 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 = WupSidePointToElem(currSide, x1, y1, tranMaster) tbl.x2, tbl.y2 = WupSidePointToElem(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 -- Combines adjacent nailing points into lines -- tblPoints Source data, only nailing points -- dirx, diry Direction of joining unit vector -- Returns table of all combined and point nailings function WupCombineNailPts(tblPoints, dirx, diry) local i1, i2, v1, v2, dx, dy, len, spacing, tblRes, nRes tblRes={} nRes=0 i1=1 while true do v1=tblPoints[i1] if v1==nil then break end -- Scan until no match, i2 will be 1 past last collected point spacing=nil i2=i1 while true do i2=i2+1 v2=tblPoints[i2] if v2==nil or v1.nailindex~=v2.nailindex then break end dx=v2.x1-v1.x1 dy=v2.y1-v1.y1 len=math.sqrt(dx*dx + dy*dy) if len<0.001 then break end dx=dx/len dy=dy/len if math.abs(dx-dirx)>0.001 or math.abs(dy-diry)>0.001 then -- Bad dir break end if spacing==nil then spacing=len -- Second point else if math.abs(len-(i2-i1)*spacing)>0.001 then break -- Spacing does not match end end end -- Save as point first nRes=nRes+1 tblRes[nRes]=v1 if i2>i1+2 then -- Something to join!!! Duplicate not to tamper original item v1=deepcopy(v1) tblRes[nRes]=v1 v1.spacing=spacing v1.x2=tblPoints[i2-1].x1 v1.y2=tblPoints[i2-1].y1 v1.point=nil i1=i2 -- Advance to first incompatible else i1=i1+1 -- Advance to next end end return tblRes end function ToMM10(d) return math.floor(d*10000+0.499)*0.0001 -- For negative math.round(-2.1)=-3 end function WupMarkingStr(line) return string.format("PML;\r\nPP %s,%s,0,122,%s;\r\nPP %s,%s,0,122,%s;\r\n", WupLen2Str(line.p0.x), WupLen2Str(line.p0.y), WupLen2Str(line.p0.z), WupLen2Str(line.p1.x), WupLen2Str(line.p1.y), WupLen2Str(line.p1.z)) end function WupIsPointLessThan(p0, p1) if math.abs(p1.x-p0.x)> 0.001 then return p0.x < p1.x end return p0.y < p1.y end function WupGetDist2D(p0, p1) local diffX=p1.x-p0.x local diffY=p1.y-p0.y return math.sqrt(diffX*diffX + diffY*diffY) end function WupGetLength2D(vec) return math.sqrt(vec.x*vec.x + vec.y*vec.y) end function WupSubtract2D(vecA, vecB) local result={} result.x=vecA.x - vecB.x result.y=vecA.y - vecB.y return result end function WupAdd2D(vecA, vecB) local result={} result.x=vecA.x + vecB.x result.y=vecA.y + vecB.y return result end function WupMultiplty2D(vec, scalar) local result={} result.x=vec.x * scalar result.y=vec.y * scalar return result end function WupGetMinMaxPoint(p0, p1) if WupIsPointLessThan(p0, p1) then return p0, p1 end return p1, p0 end function WupProjectPoint(lineP0, lineP1, point) local vecA=WupSubtract2D(lineP1, lineP0) local vecB=WupSubtract2D(point, lineP0) local lenA=WupGetLength2D(vecA) local length=CaclDot2D(vecA, vecB)/(lenA*lenA) return WupAdd2D(lineP0, WupMultiplty2D(vecA, length)) end function WupFormatPoint(point) return string.format("(%s, %s)", ToMM10(point.x), ToMM10(point.y)) end function WupFormatLine(line) return string.format("%s, %s", WupFormatPoint(line.p0), WupFormatPoint(line.p1)) end -- Indicates if source and target lines are colinear within some error threshold function WupAreLinesCollinear(source, target, maxVerticalError) local sourceMin, sourceMax=WupGetMinMaxPoint(source.p0, source.p1) local projMin=WupProjectPoint(sourceMin, sourceMax, target.p0) local projMax=WupProjectPoint(sourceMin, sourceMax, target.p1) local projMin, projMax=WupGetMinMaxPoint(projMin, projMax) local verticalErrorA=WupGetDist2D(projMin, target.p0) local verticalErrorB=WupGetDist2D(projMax, target.p1) local horizontalDiff=WupGetDist2D(projMin, sourceMax) if WupGetLength2D(WupSubtract2D(projMin, sourceMin)) < WupGetLength2D(WupSubtract2D(sourceMax, sourceMin)) then horizontalDiff = horizontalDiff * -1 end return verticalErrorA < maxVerticalError and verticalErrorB < maxVerticalError, horizontalDiff end function WupCombineDistanceOK(horizontalDiff) local combineOverlappingMarkingsOnly = gnWupCombineMarkingMaxDist < 0.0 if combineOverlappingMarkingsOnly then return horizontalDiff < 0.0 and gnWupCombineMarkingMaxDist <= horizontalDiff end return horizontalDiff <= gnWupCombineMarkingMaxDist end -- Creates cnc text with combined marking lines function WupCombineMarkings(markings) if markings == nil then return "" end local cnc="" if gnWupCombineMarkingMaxDist <= -0.999 then for i = 1, #markings do cnc = cnc .. WupMarkingStr(markings[i]) end return cnc end table.sort(markings, function (lineA, lineB) local lineAMin, lineAMax = WupGetMinMaxPoint(lineA.p0, lineA.p1); local lineBMin, lineBMax = WupGetMinMaxPoint(lineB.p0, lineB.p1); return WupIsPointLessThan(lineAMin, lineBMin); end) markingsToProcess = markings while #markingsToProcess > 0 do local combined=markingsToProcess[1] local nextMarkingsToProcess={} for i = 2, #markingsToProcess do local target=markingsToProcess[i] local areCollinear, horizontalDiff = WupAreLinesCollinear(combined, target, 0.001) if areCollinear then if WupCombineDistanceOK(horizontalDiff) then local nextMin, nextMax=WupGetMinMaxPoint(target.p0, target.p1) if WupIsPointLessThan(combined.p0, combined.p1) then combined.p1=nextMax else combined.p0=nextMax end else cnc = cnc .. WupMarkingStr(combined) combined = target end else table.insert(nextMarkingsToProcess, target) end end cnc = cnc .. WupMarkingStr(combined) markingsToProcess = nextMarkingsToProcess end return cnc end -- Returns the distance the nail gun travels function WupCalcNailingLen(tblNails) local x, y, res, i, t, dx, dy if #tblNails<1 then return 0 end res=0 i=1 x=tblNails[1].x1 y=tblNails[1].y1 while true do t=tblNails[i] if not t then break end i=i+1 -- Dist from previous to current dx = x - t.x1 dy = y - t.y1 res = res + math.sqrt(dx*dx + dy*dy) x = t.x1 y = t.y1 if not t.point then -- It is a line dx = x - t.x2 dy = y - t.y2 res = res + math.sqrt(dx*dx + dy*dy) x = t.x2 y = t.y2 end end return res end -- Combines nailings and creates cnc-text -- tblNails Collected nailings with fields: x1, y1, x2, y2, spacing, nailindex (string), point (set only if item is a point: x1=x2, y1=y2. Otherwise nil). -- bJoinPoints true=try joining all the point type nailings function WupNailTblToStr(tblNails, bJoinPoints) local i, v, n, tblNailsNow local lineEps lineEps=0.001 tblNailsNow=tblNails if bJoinPoints then -- Collect all point nailings local tblPointsVert, tblPointsHor tblPointsVert={} tblPointsHor={} n=0 for i,v in ipairs(tblNails) do if v.point then n=n+1 tblPointsVert[n]=v tblPointsHor[n]=v end end if n>2 then local tblResVert, tblResHor -- Try combining with vertical lines table.sort(tblPointsVert, function (n1, n2) if math.abs(n1.x1 - n2.x1)>lineEps then return n1.x1lineEps then return n1.y1#tblResHor then tblResVert=tblResHor end if #tblResVertlineEps then return n1.x1lineEps then return n1.y1dy then -- Horizontal planks fnSort = function(p1, p2) if math.abs(p1.info.elemdata.y1 - p2.info.elemdata.y1)>EPS then return p1.info.elemdata.y1 < p2.info.elemdata.y1 end return p1.info.elemdata.x1 < p2.info.elemdata.x1 end else -- Vertical planks fnSort = function(p1, p2) if math.abs(p1.info.elemdata.x1 - p2.info.elemdata.x1)>EPS then return p1.info.elemdata.x1 < p2.info.elemdata.x1 end return p1.info.elemdata.y1 < p2.info.elemdata.y1 end end -- Sort planks tblSorted={} for k,v in pairs(tblGuid2Info) do v.guid=k tblSorted[#tblSorted+1]=v end table.sort(tblSorted, fnSort) -- And save plank order to tblGuid2Info for k,v in ipairs(tblSorted) do tblGuid2Info[v.guid].index = k end -- Then the nailings table.sort(tblNailsNow, function(n1, n2) local p1, p2, dummy, d1, d2 p1 = tblGuid2Info[n1.guid] p2 = tblGuid2Info[n2.guid] if p1.index~=p2.index then return p1.index < p2.index end -- In the same plank, then in order of the plank's dir dummy, d1 = ac_geo ("linedist", p1.info.elemdata.x1, p1.info.elemdata.y1, p1.info.elemdata.x2, p1.info.elemdata.y2, n1.x1, n1.y1) dummy, d2 = ac_geo ("linedist", p1.info.elemdata.x1, p1.info.elemdata.y1, p1.info.elemdata.x2, p1.info.elemdata.y2, n2.x1, n2.y1) return d1 < d2 end) end end -- Write the nailings tableTot={} for i,v in ipairs(tblNailsNow) do -- NR x1,y1,x2,y2,a,i; -- HACK 9/2019: Weinmann takes given coordinates and spacing and starts spacing from begin point until the end. We can use single decimal for 0,1 mm accuracy -- ArchiFrame already has adjusted the spacing to 0,1 mm but still here can come rounding problems because begin and end points roundings -- So adjust the end point to be beyond spacing value local x1, y1, x2, y2, dx, dy, len, spacing -- Round coordinates to 1/10 mm x1=ToMM10(v.x1) y1=ToMM10(v.y1) x2=ToMM10(v.x2) y2=ToMM10(v.y2) spacing=ToMM10(v.spacing) --ac_environment("tolog", string.format("x1=%f y1=%f x2=%f y2=%f v.x1=%f v.y1=%f v.x2=%f v.y2=%f spacing=%f",x1, y1, x2, y2, v.x1, v.y1, v.x2, v.y2, spacing)) dx=x2-x1 dy=y2-y1 len=math.sqrt(dx*dx + dy*dy) if len>0.001 then local intpart, decpart local maxAdjust=0.005 -- How much each end may be adjusted automatically intpart,decpart=math.modf(len/spacing) if decpart*spacingy2 then y2=elemdata.by2 end else if elemdata.by1othery2 then othery2=elemdata.by2 end end end end --toLog(string.format("y1=%f othery1=%f y2=%f othery2=%f\n", y1, othery1, y2, othery2)) -- Then mark all lowest and highest pieces if y1 and y1othery2-0.001 then elemlayer.tblOgGuids={} for i,v in ipairs(tblHor) do if v.elemdata.by2+0.001>y2 then --toLog(string.format("OG %s\n", v.guid)) elemlayer.tblOgGuids[v.guid]=true end end end 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 -- tblNails Nailings in parent element coordinates -- Returns 1-based index of planks containing fields as WupWritePlank. Also field cncnail if point nailings function WupDoCoreLayer(tranMaster, elemlayer, tblPlanks, tblDrills, tblNails, zLayer, tblMarkings) local tblRes={} local nRes=0 local i, v, plankInfo local sNail sNail="20" if gsWupNailGun then sNail=gsWupNailGun end WupMakeOgUgTables(tranMaster, elemlayer, tblPlanks) af_request("elem_openlayer", elemlayer.guid) for i,v in ipairs(tblPlanks) do ac_objectopen(v) plankInfo=af_request("plankinfo", nil, g_infoMasterZCam) nRes=nRes+1 tblRes[nRes]=WupWritePlank(v, plankInfo, tranMaster, elemlayer, nil, tblDrills) if tblRes[nRes] then tblRes[nRes].cncnail=WupGetNailDrillCnc(v, tranMaster, plankInfo, nil, nil, false, tblNails, sNail, zLayer, tblMarkings) -- tblDrills nil kapuloille else nRes=nRes-1 end ac_objectclose() end af_request("elem_closelayer") return tblRes end -- Handles planks on paneling layer. Params as in WupDoCoreLayer -- tblNails Nailings in parent element coordinates -- sPlaPlank For paneling/studding "PLI1-10" / "PLA1-10", for secondary framing "" function WupDoExtraPlankLayer(tranMaster, elemlayer, tblPlanks, tblDrills, tblNails, sPlaPlank, zLayer, tblMarkings) local tblRes={} local nRes=0 local i, v, plankInfo, s, currItem local sNail sNail="20" if gsWupNailGun then sNail=gsWupNailGun end af_request("elem_openlayer", elemlayer.guid) for i,v in ipairs(tblPlanks) do ac_objectopen(v) plankInfo=af_request("plankinfo", nil, g_infoMasterZCam) currItem=WupWritePlank(v, plankInfo, tranMaster, elemlayer, sPlaPlank, tblDrills) if currItem then nRes=nRes+1 tblRes[nRes]=currItem s=WupGetNailDrillCnc(v, tranMaster, plankInfo, nil, nil, false, tblNails, sNail, zLayer, tblMarkings) -- tblDrills nil kapuloille currItem.cnc=currItem.cnc .. s -- Just point nailings, others are collected if currItem.module~="" and gtblWupModulItems then -- Belongs to a module which can be just in the framing layer - save to be processed in it and skip local exist, imod, vmod -- Check if exists already since this may be called multiple times (for markings) exist=false for imod,vmod in ipairs(gtblWupModulItems) do if vmod.plankinfo.ptr==currItem.plankinfo.ptr then exist=true break end end if not exist then gtblWupModulItems[#gtblWupModulItems+1]=currItem end tblRes[nRes]=nil nRes=nRes-1 end end ac_objectclose() end af_request("elem_closelayer") return tblRes end function WupIsCorner(tblPoly, x, y) if math.abs(x-tblPoly.x1)0.99 or z<-0.99 then cutLineSide=WupFindMinZSide(plankInfo, tranElem, 1, 3) else cutLineSide=WupFindMinZSide(plankInfo, tranElem, 2, 4) end -- Frame plank has all machinings in single table including beginning and ending shapes -- Plank direction in element coordinates local wupx1, wupy1 local currSide, zoff currSide=plankInfo.tblSides[cutLineSide] wupx1,wupy1,zoff=WupSidePointToElem(currSide, 0, 0, tranElem) return zoff end -- Gives string for the raw boards (PLAx) -- elemlayer Layer from af_request("elem_openparent") -- tblBoards 1-based table of boards to handle for this element, adds fields plankinfo, thickness, elemx1, elemy1, elemx2, elemy2 into each board -- tranMaster Translation matrix from global coordinates to the element's coordinate system -- sPlaBoard The wup-command "PLI1-10" / "PLA1-10" -- matRef The wup index (material reference) -- tblDrills Drills in parent element coordinates -- tblSawCuts Saw cuts in parent element coordinates -- tblNails Nailings in parent element coordinates -- nPsgSign +-1, sign to get into core layer (defines sign of sawing depth) -- Returns cnc-string, cnc for nails (only point nailings) function WupDoBoardsLayer(tranMaster, elemlayer, tblBoards, sPlaBoard, matRef, zLayer, tblDrills, tblSawCuts, tblNails, nPsgSign, tblMarkings) local i, v, sRes, s, sCncNails, sBoardLayer, polyCnc, zBoard local sNail sNail="10" if gsWupNailGun then sNail=gsWupNailGun end -- Calculate position in elem coordinates af_request("elem_openlayer", elemlayer.guid) for i,v in ipairs(tblBoards) do v.poly=af_request("getpoly", nil, v.guid) ac_objectopen(v.guid) v.plankinfo=af_request("plankinfo") v.thickness=ac_objectget("iThickness") -- 6/2024: Perhaps should use: zBoard = WupGetBoardZ(v.plankinfo, tranMaster) v.cncnail=WupGetNailDrillCnc(v.guid, tranMaster, v.plankinfo, tblDrills, tblSawCuts, false, tblNails, sNail, zLayer, tblMarkings) ac_objectclose() -- Calculate xy-coordinates in parent (polygon is on board front face) v.elemx1,v.elemy1=WupSidePointToElem(v.plankinfo.tblSides[2], v.poly.x1, v.poly.y1, tranMaster) v.elemx2,v.elemy2=WupSidePointToElem(v.plankinfo.tblSides[2], v.poly.x2, v.poly.y2, tranMaster) --ac_environment("tolog", string.format("v.elemx1=%f, v.elemy1=%f", v.elemx1, v.elemy1)) end af_request("elem_closelayer") table.sort(tblBoards, function (n1, n2) if math.abs(n1.elemx1-n2.elemx1)>0.001 then return n1.elemx1first 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 WupIsNoDir(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 WupCheckSpecCut(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", WupLen2Str(c1.x-0.047), WupLen2Str(c1.y-0.050), WupLen2Str(depth), WupLen2Str(z) ) -- 121: 100=Tool on left, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,121,0,%s;\r\n", WupLen2Str(c2.x+0.047), WupLen2Str(c1.y-0.050), WupLen2Str(depth), WupLen2Str(z) ) -- Upper s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,221,15, %s;\r\n", WupLen2Str(c1.x-0.049), WupLen2Str(c1.y-0.007), WupLen2Str(depth), WupLen2Str(z) ) -- 221: 200=Tool on right, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,221,15, %s;\r\n", WupLen2Str(c2.x+0.049), WupLen2Str(c1.y-0.007), WupLen2Str(depth), WupLen2Str(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", WupLen2Str(c1.x-0.049), WupLen2Str(c1.y+0.046-0.003), WupLen2Str(depth), WupLen2Str(z) ) -- 221: 200=Tool on right, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,221,15,%s;\r\n", WupLen2Str(c2.x+0.049), WupLen2Str(c1.y+0.046-0.003), WupLen2Str(depth), WupLen2Str(z) ) -- Lower s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,121,0,%s;\r\n", WupLen2Str(c1.x-0.049), WupLen2Str(c1.y), WupLen2Str(depth), WupLen2Str(z) ) -- 121: 100=Tool on left, 20=undercut, 1=saw blade s=s .. string.format( "PP %s,%s,%s,121,0,%s;\r\n", WupLen2Str(c2.x+0.049), WupLen2Str(c1.y), WupLen2Str(depth), WupLen2Str(z) ) return s end end return nil end -- nPsfType==nil: PP 1252,1364,-13,221,0,0; -- If not nil: PP 1252,1364,-13,0,0,0; function WupGetPP( nPsfType, x, y, depth, sControl, z ) if nPsfType then return string.format( "PP %s,%s,%s,%d,0,%s;\r\n", WupLen2Str(x), WupLen2Str(y), WupLen2Str(depth), nPsfType, WupLen2Str(z) ) else return string.format( "PP %s,%s,%s,%s,0,%s;\r\n", WupLen2Str(x), WupLen2Str(y), WupLen2Str(depth), sControl, WupLen2Str(z) ) end end -- Shortens begin and end unless skipped -- Returns: true=ok, false=skip whole line -- Then x1, y1, x2, y2 function WupShortenLine(x1, y1, x2, y2, doBeg, doEnd, dShorten) local dx, dy, len, n n=0 if doBeg then n=n+1 end if doEnd then n=n+1 end --ac_environment("tolog", string.format("pre: x1=%f, y1=%f, x2=%f, y2=%f, doBeg=%s, doEnd=%s", x1, y1, x2, y2, tostring(doBeg), tostring(doEnd))) dx=x2-x1 dy=y2-y1 len=math.sqrt(dx*dx + dy*dy) if len<0.001 or len0.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 WupShowErrMsg( "WARNING: POLYGON WITH HOLES WRITTEN - CASE NOT TESTED ***"); begInd=i+1 prev=nil end end -- Process the lines local nextprev sCnc="" i=1 prev=nil -- Set if c1 written at last round if nPsfType==nil then if sType=="PSF" then nPsfType=0 -- true in lua anyway end end local sControl if nPsfType 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 (Note 7.6.2021: sControl not used if nPsfType != nil) 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 WupShowErrMsg( "ERROR: WupGetLayerPoly/v.cutfromobj~=nil. CONTACT SUPPORT" ) end nextprev=v if vecNoCuts and WupIsNoDir(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", WupLen2Str(prev.c2.x), WupLen2Str(prev.c2.y), WupLen2Str(depth), sControl, WupLen2Str(z) ) s=WupGetPP(nPsfType, prev.c2.x, prev.c2.y, depth, sControl, z) sCnc=sCnc .. s end nextprev=nil else s=WupCheckSpecCut(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", WupLen2Str(prev.c2.x), WupLen2Str(prev.c2.y), WupLen2Str(depth), sControl, WupLen2Str(z) ) sCnc=sCnc .. WupGetPP(nPsfType, 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", WupLen2Str(v.c1.x), WupLen2Str(v.c1.y), WupLen2Str(depth), sControl, WupLen2Str(z) ) s=WupGetPP(nPsfType, 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", WupLen2Str(v.c2.x), WupLen2Str(v.c2.y), WupLen2Str(depth), sControl, WupLen2Str(z) ) s=WupGetPP(nPsfType, 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 WupIsNoDir(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=WupCheckSpecCut(tranMaster, currSide, v, bHole, depth, z, sType, nWupOpeningBotTop) end if s then sCnc=sCnc .. s else -- Write whole line local x1,y1,x2,y2, lineOk, shortenBeg, shortenEnd x1=v.c1.x y1=v.c1.y x2=v.c2.x y2=v.c2.y lineOk=true shortenBeg=(not v.overcut1) shortenEnd=(not v.overcut2) if bHole then shortenBeg=not shortenBeg shortenEnd=not shortenEnd end if nShortenUnderCut and math.abs(nShortenUnderCut)>EPS and (shortenBeg or shortenEnd) then lineOk,x1,y1,x2,y2=WupShortenLine(x1, y1, x2, y2, shortenBeg, shortenEnd, nShortenUnderCut) end if lineOk then sCnc=sCnc .. string.format( "%s;\r\n", sType ) -- BEG controlNow=nControl+underCut -- Undercut if v.overcut1 then controlNow=nControl+overCut -- overcut end s=WupGetPP(nPsfType, x1, y1, depth, string.format("%d", controlNow), z) sCnc=sCnc .. s -- END controlNow=nControl+underCut -- Undercut if v.overcut2 then controlNow=nControl+overCut -- overcut end s=WupGetPP(nPsfType, x2, y2, depth, string.format("%d", controlNow), z) sCnc=sCnc .. s end end end i=i+1 end end return sCnc end gtblWupCnc=nil -- If set, will be collected into the buf. Otherwise to io.write gnWupCnc=0 -- Index to previous if collecting gbWupWriteNil=false -- Set to true not write anything function WupWriteCnc( s ) if s==nil then error("WupWriteCnc(nil)") end if s=="" then return end if gtblWupCnc then gnWupCnc=gnWupCnc+1 gtblWupCnc[gnWupCnc]=s elseif not gbWupWriteNil then io.write(s) end end function WupGetSawCutCnc(sType, vsaw, depth, zLayer) local s s="" s=s .. string.format( "%s;\r\n", sType ) s=s .. string.format( "PP %s,%s,%s,121,%s, %s;\r\n", WupLen2Str(vsaw.x1), WupLen2Str(vsaw.y1), WupLen2Str(depth), WupInt2Str(vsaw.angledeg), WupLen2Str(zLayer) ) s=s .. string.format( "PP %s,%s,%s,121,%s, %s;\r\n", WupLen2Str(vsaw.x2), WupLen2Str(vsaw.y2), WupLen2Str(depth), WupInt2Str(vsaw.angledeg), WupLen2Str(zLayer) ) -- 121: 100=Tool on left (200=right), 20=undercut, 1=saw blade return s end function WupGetPolyMid(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 -- tranMaster Transformation to the core layer -- tblPlanks All planks -- kplank Index/key to current plank of vplank -- panelParam nil for core, PLx for others -- Returnst three values: -- cnc text for the whole module transformed to be relative to modules left bottom -- nail cnc not transformed (as it is currently in the planks), may be nil -- next key to tblPlanks function WupHandleModule(tranMaster, tblPlanks, kplank, elemlayer, panelParam) -- Find last item and the bounding box local kend, v, module, x1, x2, y1, y2, z1, z2, cnc, cncnail, tblRes -- Find bounding box & make nailings cnc v=tblPlanks[kplank] module=v.module x1=v.plankinfo.elemdata.bx1 x2=v.plankinfo.elemdata.bx2 y1=v.plankinfo.elemdata.by1 y2=v.plankinfo.elemdata.by2 z1=v.plankinfo.elemdata.bz1 z2=v.plankinfo.elemdata.bz2 cncnail=nil kend=kplank -- Process current also while true do v=tblPlanks[kend] if v==nil or v.module~=module then break end kend=kend+1 if x1>v.plankinfo.elemdata.bx1 then x1=v.plankinfo.elemdata.bx1 end if x2v.plankinfo.elemdata.by1 then y1=v.plankinfo.elemdata.by1 end if y2v.plankinfo.elemdata.bz1 then z1=v.plankinfo.elemdata.bz1 end if z2z2 then -- Should be always z1,z2=z2,z1 end cnc=string.format("MODUL %s, %s, %s, %s, %s, %s, %s;\r\n", WupLen2Str(x2-x1), WupLen2Str(y2-y1), WupLen2Str(z2-z1), WupLen2Str(x1), WupLen2Str(y1), WupStrToWup(module), WupLen2Str(z1)) local tranModul tranModul=deepcopy(tranMaster) tranModul[4]=tranModul[4] - x1 * g_infoMaster.vecx.x - y1 * g_infoMaster.vecz.x - z1 * g_infoMaster.vecy.x tranModul[8]=tranModul[8] - x1 * g_infoMaster.vecx.y - y1 * g_infoMaster.vecz.y - z1 * g_infoMaster.vecy.y tranModul[12]=tranModul[12] - x1 * g_infoMaster.vecx.z - y1 * g_infoMaster.vecz.z - z1 * g_infoMaster.vecy.z while kplankEPS and (v.overbeg==0 or v.overend==0) then local shortenBeg, shortenEnd shortenBeg=false shortenEnd=false if v.overbeg==0 then shortenBeg=true end if v.overend==0 then shortenEnd=true end lineOk,x1,y1,x2,y2=WupShortenLine(x1, y1, x2, y2, shortenBeg, shortenEnd, nShortenUnderCut) end if lineOk then -- 200=Tool on the right, [20=undercut, 10=overcut], 1=Cylindrical trimmer local nControl, nControlBeg, nControlEnd, angle, swapCamera, side, depthNow, sPsgPaf, objTool -- TODO: check if reversed dir, handle bladeside, angle swapCamera=false nControl=1 -- Standard saw blade -- Check the tool number from the saw cut object sPsgPaf="PSG" ac_objectopen(v.elemguid) objTool=ac_objectget("iToolType") if objTool and objTool==2 then -- It is a router sPsgPaf="PAF" end ac_objectclose() side=v.bladeside if swapCamera then side=-side end if side<0 then nControl=nControl+100 elseif side>0 then nControl=nControl+200 end nControlBeg=nControl if v.overbeg>0 then nControlBeg=nControlBeg+10 else nControlBeg=nControlBeg+20 end nControlEnd=nControl if v.overend>0 then nControlEnd=nControlEnd+10 else nControlEnd=nControlEnd+20 end depthNow=depth if math.abs(v.depth)>0.0001 then local sgn=GetSgn(depth) depthNow=v.depth+extraDepth if sgn~=GetSgn(depthNow) and sgn~=0 then depthNow=-depthNow end end angle=v.angledeg if swapCamera then angle=-angle end s=string.format("%s 2;\r\nPP %s,%s,%s,%d,%0.1f,%s;\r\n", sPsgPaf, WupLen2Str(x1), WupLen2Str(y1), WupLen2Str(depthNow), nControlBeg, angle, WupLen2Str(zSaw)) s=s .. string.format("PP %s,%s,%s,%d,%0.1f,%s;\r\n", WupLen2Str(x2), WupLen2Str(y2), WupLen2Str(depthNow), nControlEnd, angle, WupLen2Str(zSaw)) --ac_environment("tolog", s) if v.ishole==1 then cncHoles=cncHoles .. s else cncMain=cncMain .. s end end end if contourFirst and cncMain~="" then WupWriteCnc(cncMain) cncMain="" end -- Write holes WupWriteCnc(cncHoles) return cncMain end -- If guid is nil, the board is already opened with ac_objectopen(). Otherwise the part is opened here -- Returns: true=not a board or board going to cnc, false=was a board having iCnc==0 function WupExportPart(guid) local info, cnc, res if guid then ac_objectopen(guid) end res = true info = af_request("plankinfo") if info and info.type == 4 then cnc = ac_objectget("iCnc") if cnc and cnc == 0 then res = false end end if guid then ac_objectclose() end return res end -- Writes single layer (either planks or boards) -- sPlaPlank PLA/I1-10 for planks -- sPlaBoard PLA/I1-10 for boards -- nPsgSign +-1, sign to get into core layer (defines sign of sawing depth) -- Returns: 0=no PLx-code used, 1=PLx code used function WupWriteElemLayer(tranMaster, vlayer, sPlaPlank, sPlaBoard, mapElemToPlanks, bCore, nPsgSign) local infoOwner, quant, tblDrills, tblNails, sCncNails, s, plankInfo, zLayer, vecNoCuts, panelParam local retVal retVal=0 infoOwner=af_request("plankinfo", vlayer.guid) quant=af_request("elem_quantities", vlayer.guid, 0).tblelems[1] -- For this single layer only gsWupNailGun=nil gsWupMatIndex="" gsWupIsFraming="" gsWupMatName="" gbWupMatNameForBoard=false if quant.layertypenum==1 then gbWupMatNameForBoard=true end if quant.xmlutf8 then -- At the end of the definition there is -- Quick&dirty: no real parsing gsWupNailGun=string.match(quant.xmlutf8, " cnc_nailgun=\"(.-)\"") if gsWupNailGun and gsWupNailGun=="" then -- Not necessarily used: ac_environment("tolog", string.format("WUP-WARNING: cnc_nailgun for layer %s is empty - using default nail index", vlayer.type)) gsWupNailGun=nil end s=string.match(quant.xmlutf8, " cnc_matindex=\"(.-)\"") if s then gsWupMatIndex=s end s=string.match(quant.xmlutf8, " cnc_matname=\"(.-)\"") if s then gsWupMatName=s end s=string.match(quant.xmlutf8, " cnc_isframing=\"(.-)\"") if s then gsWupIsFraming=s end end plankInfo=af_request("plankinfo", vlayer.guid) s,zLayer=WupFindMinZSide(plankInfo, tranMaster, 2, 4) tblDrills={} tblNails={} tblMarkings={} sCncNails="" -- Handle planks tblPlanks=mapElemToPlanks[vlayer.guid] if gtblWupCollectElems then -- Save element layer to last item local tbl tbl={} tbl.elem=vlayer tbl.quant=quant tbl.tblplanks=tblPlanks gtblWupCollectElems[#gtblWupCollectElems].elemlayers[vlayer.type]=tbl end if quant.layertypenum==2 and quant.tblboards and tblPlanks==nil then -- Board should be exploded into planks WupShowErrMsg( 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=WupDoCoreLayer(tranMaster, vlayer, tblPlanks, tblDrills, tblNails, zLayer, tblMarkings) panelParam=nil -- Append collected MODUL pieces local i, v, dest dest=#tblPlanks for i,v in ipairs(gtblWupModulItems) do dest=dest+1 tblPlanks[dest]=v end else local sPla sPla=sPlaPlank if gsWupIsFraming and gsWupIsFraming~="" then if gsWupIsFraming=="1" then sPla="" end elseif string.match(quant.elemtype, "intstud.*") then sPla="" -- Scondary framing end if sPla and sPla~="" then retVal=1 end tblPlanks=WupDoExtraPlankLayer(tranMaster, vlayer, tblPlanks, tblDrills, tblNails, sPla, zLayer, tblMarkings) panelParam=sPlaPlank end table.sort(tblPlanks, function (n1, n2) -- Put modules last, ""=no module if n1.module~=n2.module then return n1.module0.001 then return n1.x1