-- ArchiFrame BTL 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 EMcFrAngledBegTenonMort = 112 EMcVCutBeg = 114 EMcFrAngledEndOld = 200 EMcFrAngledEnd = 201 EMcFrAngledEndTenon = 210 EMcFrEndHiddenShoe = 211 EMcFrAngledEndTenonMort = 212 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 EMcFrBalkShoe = 1000 -- CLT Machining edge types EMcEdgeGroove = 2000 --------------------------------------------------------------------------- -- ARCHIFRAME & ARCHILOGS COMMON FUNCTIONS function toLog(str) ac_environment("tolog", str) end -- Dumps given variable into string function DumpTblInt(o) local s 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 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 -- Finds key=val from string having \n as separator -- Returns: nil=not found, str=value for requested key function FindKeyVal(strData, keyFind) local i1,i2,s2,firstmsg firstmsg=true i1=1 while i1=1 do if string.sub(id,pos,pos)==s then break end pos=pos-1 end elemid="" if pos>1 then elemid=string.sub(id, 1, pos-1) end id=string.sub(id, pos+1) return elemid, id end -- Splits material ID to two parts from last space. If no space, last part will be "" function SplitMatId(id, plankinfo) local elemid, s, id, grade, pos s=" " -- Separator id=ac_objectget("iMatId") pos=string.len(id) while pos>=1 do if string.sub(id,pos,pos)==s then break end pos=pos-1 end grade="" if pos>1 then grade=string.sub(id, pos+1) id=string.sub(id, 1, pos-1) end return id, grade end function IsIBeam() local matId matId=ac_objectget("iMatId") if string.sub(matId, 1, 2)=="H " or string.sub(matId, 1, 3)=="HI " or string.sub(matId, 1, 3)=="HB " then return true end return false end -- COMMON FUNCTION IN A FEW PLACES IN DATA-FOLDER, UPDATE ALL IF UPDATING ONE -- isHor Is it horizontal structure (false/nil=default) -- lang eng/swe/fin -- elemgroup Parameter iElemGroup function GetElemGroupName(isHor, elemgroup) local lang lang=af_request("aflang") if isHor then -- Horizontal structure if elemgroup==nil then elemgroup="" elseif string.match(elemgroup, "^top%a*") or string.match(elemgroup, "^2ndtop%a*") or string.match(elemgroup, "^contour_x.*") then elemgroup="Stud" if lang=="fin" then elemgroup="Tolppa" elseif lang=="swe" then elemgroup="Regel" end elseif string.match(elemgroup, "^bottom%a*") or string.match(elemgroup, "^2ndbottom%a*") then elemgroup="Stud" if lang=="fin" then elemgroup="Tolppa" elseif lang=="swe" then elemgroup="Regel" end elseif string.match(elemgroup, "^vertical_x%a*") or string.match(elemgroup, "^vertical_y%a*") or string.match(elemgroup, "^contourtilted_opening%a*") then elemgroup="Door/win" if lang=="fin" then elemgroup="Ikk/ovi" elseif lang=="swe" then elemgroup="Avväxling" end elseif string.match(elemgroup, "^vertical%a*") then elemgroup="Hor" if lang=="fin" then elemgroup="Vaaka" end elseif string.match(elemgroup, "^contourtilted%a*") then elemgroup="Angled" if lang=="fin" then elemgroup="Vinojuoksu" elseif lang=="swe" then elemgroup="Regel" end elseif string.match(elemgroup, "^balk.*") then elemgroup="Beam" if lang=="fin" then elemgroup="Palkki" elseif lang=="swe" then elemgroup="Bärlina" end elseif string.match(elemgroup, "^lintel.*") then elemgroup="W/D beam" if lang=="fin" then elemgroup="Aukkopalkki" elseif lang=="swe" then elemgroup="Bärplanka" end elseif string.match(elemgroup, "^reinforce.*") then elemgroup="Reinforcement" if lang=="fin" then elemgroup="Vahvike" elseif lang=="swe" then elemgroup="Förstärkning" end elseif string.match(elemgroup, "^nogging.*") then elemgroup="Nogging" if lang=="nor" then elemgroup="Kubbing" elseif lang=="fin" then elemgroup="Nurjahdustuki" elseif lang=="swe" then elemgroup="Kortling" end elseif elemgroup=="sideleft_spacing" or elemgroup=="sideright_spacing" then elemgroup="Side beams" if lang=="fin" then elemgroup="Sivupalkki" elseif lang=="swe" then elemgroup="Ändkortling" end else elemgroup="" -- Just leave it empty, earlier was "? (elemgroup)" end else -- Vertical structure if elemgroup==nil then elemgroup="" elseif string.match(elemgroup, "^top%a*") or string.match(elemgroup, "^2ndtop%a*") then elemgroup="Top plate" if lang=="fin" then elemgroup="Yläjuoksu" elseif lang=="swe" then elemgroup="HB" end elseif string.match(elemgroup, "^bottom%a*") or string.match(elemgroup, "^2ndbottom%a*") then elemgroup="Bottom plate" if lang=="fin" then elemgroup="Alajuoksu" elseif lang=="swe" then elemgroup="Syll" end elseif string.match(elemgroup, "^contour_x.*") then elemgroup="Top/bottom plate" if lang=="fin" then elemgroup="Ylä/alajuoksu" elseif lang=="swe" then elemgroup="HB/Syll" end elseif string.match(elemgroup, "^vertical_x%a*") or string.match(elemgroup, "^contourtilted_opening%a*") then elemgroup="Win/door" if lang=="fin" then elemgroup="Ikk/ovi" elseif lang=="swe" then elemgroup="Avväxling" end elseif string.match(elemgroup, "^vertical%a*") or string.match(elemgroup, "^contour_y.*") then elemgroup="Stud" if lang=="fin" then elemgroup="Tolppa" elseif lang=="swe" then elemgroup="Regel" end elseif string.match(elemgroup, "^contourtilted%a*") then elemgroup="Angled" if lang=="fin" then elemgroup="Vinojuoksu" elseif lang=="swe" then elemgroup="Regel" end elseif string.match(elemgroup, "^balktop.*") then elemgroup="Beam top" if lang=="fin" then elemgroup="Palkki ylä" elseif lang=="swe" then elemgroup="Bärlina topp" end elseif string.match(elemgroup, "^balkbot.*") then elemgroup="Beam bottom" if lang=="fin" then elemgroup="Palkki ala" elseif lang=="swe" then elemgroup="Bärlina botten" end elseif string.match(elemgroup, "^lintel.*") then elemgroup="W/D beam" if lang=="fin" then elemgroup="Aukkopalkki" elseif lang=="swe" then elemgroup="Bärplanka" end elseif string.match(elemgroup, "^nogging.*") then elemgroup="Nogging" if lang=="nor" then elemgroup="Kubbing" elseif lang=="fin" then elemgroup="Nurjahdustuki" elseif lang=="swe" then elemgroup="Kortling" end elseif elemgroup=="sideleft_spacing" or elemgroup=="sideright_spacing" then elemgroup="Side beams" if lang=="fin" then elemgroup="Sivupalkki" elseif lang=="swe" then elemgroup="Ändkortling" end else elemgroup="" -- Just leave it empty, earlier was "? (elemgroup)" end end return elemgroup end -- nInfo One of EInfoXxxx function GetInfoStr(nInfo, plankinfo) local s, s2 s="" if nInfo==EInfoFullIDPlusUsage then s=ac_objectget("iUsageId") if not s then s="" end if s~="" then s=string.format( "%s (%s)", gsCurrentLogId, s) else s=gsCurrentLogId end elseif nInfo==EInfoShortID then s2, s=SplitId(gsCurrentLogId, plankinfo) elseif nInfo==EInfoFullID then s=gsCurrentLogId elseif nInfo==EInfoElementID then s, s2=SplitId(gsCurrentLogId, plankinfo) elseif nInfo==EInfoGrade then s2, s=SplitMatId() elseif nInfo==EInfoUsage then s=ac_objectget("iUsageId") elseif nInfo==EInfoMatIdFull then s=ac_objectget("iMatId") elseif nInfo==EInfoMatIdShort then s, s2=SplitMatId() elseif nInfo==EInfoMatIdIfNotSize then s=ac_objectget("iMatId") if s==string.format("%.0fx%.0f", gnCurrWidth*1000, gnCurrHeight*1000) then s="" end elseif nInfo==EInfoPlankRole or nInfo==EInfoPlankRoleIdFull then s=ac_objectget("iElemGroup") s=GetElemGroupName(false, s) elseif nInfo==EInfoPlankRoleFin or nInfo==EInfoPlankRoleIdFullFin then -- Obsolete s=ac_objectget("iElemGroup") if s==nil then s="" elseif string.match(s, "^top%a*") or string.match(s, "^2ndtop%a*") then s="Yläjuoksu" elseif string.match(s, "^bottom%a*") or string.match(s, "^2ndbottom%a*") then s="Alajuoksu" elseif string.match(s, "^vertical_x%a*") or string.match(s, "^contourtilted_opening%a*") then s="Ikk/ovi" elseif string.match(s, "^vertical%a*") then s="Tolppa" elseif string.match(s, "^contourtilted%a*") then s="Vinojuoksu" elseif string.match(s, "^balk%a*") then s="Palkki" elseif string.match(s, "^lintel.*") then s="Aukkopalkki" end if nInfo==EInfoPlankRoleIdFullFin then s=s .. " (" .. GetInfoStr(EInfoFullID, plankinfo) .. ")" end elseif nInfo==EInfoPlankRoleNor or nInfo==EInfoPlankRoleIdFullNor then s=ac_objectget("iElemGroup") if s==nil then s="" elseif string.match(s, "^top%a*") or string.match(s, "^2ndtop%a*") then s="Toppsvill" elseif string.match(s, "^bottom%a*") or string.match(s, "^2ndbottom%a*") then s="Bunnsvill" elseif string.match(s, "^vertical_x%a*") or string.match(s, "^contourtilted_opening%a*") then s="Vindu/dør" elseif string.match(s, "^vertical%a*") then s="Stender" elseif string.match(s, "^contourtilted%a*") then s="Vinkel" elseif string.match(s, "^balk%a*") then s="Bjelke" elseif string.match(s, "^lintel.*") then s="Overdekning" end if nInfo==EInfoPlankRoleIdFullNor then s=s .. " (" .. GetInfoStr(EInfoFullID, plankinfo) .. ")" end elseif nInfo==EInfoSolidOrIBeam then s="Solid-Wood" if IsIBeam() then s="I-Beams" end elseif nInfo==EOwnerElemID then local info info=af_request("plankinfo") if info.ownerelemguid then s=ac_getobjparam(info.ownerelemguid, "#id") end end if s==nil then s="" end return s end gnBtlScaleUnit=1 -- 1:SCALEUNIT=1, 2:SCALEUNIT=2 -- Converts metric length to btl-length str (8 digits for now) function BtlLen2Str(val) local s if gnBtlScaleUnit==2 then s=string.format("%08.0f", val*100000) elseif gnBtlScaleUnit==1 then s=string.format("%08.0f", val*10000) else error("Bad gnBtlScaleUnit") 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 number to btl str using current SCALEUNIT function BtlNum2Str(val) local s if gnBtlScaleUnit==2 then s=string.format("%08.0f", val*100) elseif gnBtlScaleUnit==1 then s=string.format("%08.0f", val*10) else error("Bad gnBtlScaleUnit") end if string.len(s)>8 then if gnWarnings==nil then gnWarnings=0 end if gnWarnings<100 then AddCncErr(0, string.format("ERROR: VALUE TOO LONG (MAX 8 DIGITS): %s", s)) gnWarnings=gnWarnings+1 end end return s end -- Converts int to btl-length str (8 digits for now) function BtlInt2Str(val) return BtlNum2Str(val) end --------------------------------------------------------------------------- -- ARCHIFRAME & ARCHILOGS GEOMETRY FUNCTIONS -- ### 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 function CalcCross2D( v1, v2 ) x,y,z=CalcCross3D(v1.x, v1.y, 0, v2.x, v2.y, 0) return 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 function ToUnitVec2(vec) local d=1.0/math.sqrt(vec.x*vec.x+vec.y*vec.y) return Mul2D(vec, d) end function Dot2(v1, v2) return v1.x*v2.x+v1.y*v2.y 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 function Sub2D(pt2, pt1) return {x=pt2.x-pt1.x, y=pt2.y-pt1.y} end function Add2D(pt1, pt2) return {x=pt2.x+pt1.x, y=pt2.y+pt1.y} end function Mul2D(vec, factor) return {x=vec.x*factor, y=vec.y*factor} end function Distance2D(pt1, pt2) local vec=Sub2D(pt2, pt1) return math.sqrt(vec.x*vec.x+vec.y*vec.y) 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 Real distance from x1,y1,z1 in line's direction 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, len dist=0 x=x2-x1 y=y2-y1 z=z2-z1 len=math.sqrt(x*x + y*y + z*z) 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*len 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 len(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)PI then nAngle=-2*PI+nAngle elseif nAngle<-PI then nAngle=2*PI+nAngle end s = string.format( "PROCESSKEY: %d-013-%d SAW CUT\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P06:%s P07:%s P08:%s P11:%s P12:%s", 4, -- X-013-1 nBtlSide, -- 1-013-X BtlLen2Str(xc), -- P01 BtlLen2Str(yc), -- P02 BtlNum2Str(nAngle / PI * 180), -- P06 BtlNum2Str(nBevel / PI * 180), -- P07 BtlNum2Str(angleBottom / PI * 180), -- P08 BtlLen2Str(depth), -- P11 BtlLen2Str(len) -- P12 ) end BtlAddMc( xc, s ) end -- Returns side (1-4) info for tenon/mortise as a table with fields: -- x,y,z origin -- dy size in y dir -- vecy y-vector (x in plank's dir) -- vecz Normal vector outside function GetLocalTenonSide(nSide) local res, y0, z0, vecy_y, vecy_z, dy, vecz_y, vecz_z y0=0 z0=0 vecy_y=0 vecy_z=0 vecz_y=0 vecz_z=0 if nSide==1 then vecy_y=-1 y0=gnCurrWidth z0=gnCurrHeight dy=gnCurrWidth vecz_z=1 elseif nSide==2 then vecy_z=-1 z0=gnCurrHeight dy=gnCurrHeight vecz_y=-1 elseif nSide==3 then vecy_y=1 dy=gnCurrWidth vecz_z=-1 elseif nSide==4 then vecy_z=1 y0=gnCurrWidth dy=gnCurrHeight vecz_y=1 else error("GetLocalTenonSide bad side") end res={} res.x=0 -- origin, x always at begin of the plank res.y=y0 res.z=z0 res.dy=dy res.vecy={} res.vecy.x=0 res.vecy.y=vecy_y res.vecy.z=vecy_z res.vecz={} res.vecz.x=0 res.vecz.y=vecz_y res.vecz.z=vecz_z return res end function DumpVec(prompt, vec) toLog(string.format("%s: %f %f %f", prompt, vec.x, vec.y, vec.z)) end -- Calculates tenon's geometry in plank's local coordinates -- x,y,z Tenon bottom center origin in plank's coords -- Returns table with fields: -- x,y,z Bottom center (AF anchor) in plank's coords -- vecx, pointing outside the tenon -- vecy, in y-axis -- vecz, tenon up -- plane Anchor plane -- opp_side Opposite side 1-4 -- opp_plane Opposite plane function CalcTenonGeo( isBeg, nAngleTopDeg, nAngleSideDeg, x, y, z, nSide, nRotAngleDeg ) local tblTenon, tblSide, t, endmul, a -- Current plane's origin, y-vector and y size tblTenon={} -- vecx t={} t.x=1 endmul=1 if isBeg then t.x=-1 endmul=-1 end t.y=0 t.z=0 tblTenon.vecx=t -- vecy tblSide=GetLocalTenonSide(nSide) tblTenon.plane=tblSide tblTenon.vecy=deepcopy(tblSide.vecy) -- vecy has zero x component, z vector goes to left from y if math.abs(tblTenon.vecy.x)>EPS then error("CalcTenonGeo err1") end tblTenon.vecz={} tblTenon.vecz.x=0 tblTenon.vecz.y=-tblTenon.vecy.z tblTenon.vecz.z=tblTenon.vecy.y -- Calc origin with unrotated axes (x components on vecy and vecz are zero) tblTenon.x=x -- x is not side dependent (is distance from the end of the plank and here the real coordinate) tblTenon.y=tblSide.y + tblSide.dy * 0.5 * tblTenon.vecy.y tblTenon.z=tblSide.z + tblSide.dy * 0.5 * tblTenon.vecy.z -- To middle of the current bottom tblTenon.y=tblTenon.y + y * tblTenon.vecy.y -- Add y offset tblTenon.z=tblTenon.z + y * tblTenon.vecy.z tblTenon.y=tblTenon.y + z * tblTenon.vecz.y -- Add z offset tblTenon.z=tblTenon.z + z * tblTenon.vecz.z --[[ toLog(string.format("PRE, x=%f y=%f z=%f tblTenon.vecy.x=%f, tblTenon.vecy.y=%f, tblTenon.vecy.z=%f, tblTenon.vecz.x=%f, tblTenon.vecz.y=%f, tblTenon.vecz.z=%f, tblSide.vecz.x=%f, tblSide.vecz.y=%f, tblSide.vecz.z=%f", tblTenon.x, tblTenon.y, tblTenon.z, tblTenon.vecy.x, tblTenon.vecy.y, tblTenon.vecy.z, tblTenon.vecz.x, tblTenon.vecz.y, tblTenon.vecz.z, tblSide.vecz.x, tblSide.vecz.y, tblSide.vecz.z)) DumpVec("vecx", tblTenon.vecx) DumpVec("vecy", tblTenon.vecy) DumpVec("vecz", tblTenon.vecz) ]] -- Rot by angle top a=-endmul*(nAngleTopDeg-90)*PI/180 tblTenon.vecx.x, tblTenon.vecx.y, tblTenon.vecx.z=RotateSingle3D(tblTenon.vecx.x, tblTenon.vecx.y, tblTenon.vecx.z, tblTenon.vecz.x, tblTenon.vecz.y, tblTenon.vecz.z, a) tblTenon.vecy.x, tblTenon.vecy.y, tblTenon.vecy.z=RotateSingle3D(tblTenon.vecy.x, tblTenon.vecy.y, tblTenon.vecy.z, tblTenon.vecz.x, tblTenon.vecz.y, tblTenon.vecz.z, a) -- Rot by angle side/bevel a=endmul*(nAngleSideDeg-90)*PI/180 tblTenon.vecx.x, tblTenon.vecx.y, tblTenon.vecx.z=RotateSingle3D(tblTenon.vecx.x, tblTenon.vecx.y, tblTenon.vecx.z, tblTenon.vecy.x, tblTenon.vecy.y, tblTenon.vecy.z, a) tblTenon.vecz.x, tblTenon.vecz.y, tblTenon.vecz.z=RotateSingle3D(tblTenon.vecz.x, tblTenon.vecz.y, tblTenon.vecz.z, tblTenon.vecy.x, tblTenon.vecy.y, tblTenon.vecy.z, a) -- Rot by tenon rot angle (looked from inside plank towards the end and beg/end will rotate to different dirs - no need for endmul) a=-nRotAngleDeg*PI/180 tblTenon.vecz.x, tblTenon.vecz.y, tblTenon.vecz.z=RotateSingle3D(tblTenon.vecz.x, tblTenon.vecz.y, tblTenon.vecz.z, tblTenon.vecx.x, tblTenon.vecx.y, tblTenon.vecx.z, a) tblTenon.vecy.x, tblTenon.vecy.y, tblTenon.vecy.z=RotateSingle3D(tblTenon.vecy.x, tblTenon.vecy.y, tblTenon.vecy.z, tblTenon.vecx.x, tblTenon.vecx.y, tblTenon.vecx.z, a) --[[ toLog(string.format("FINAL VEC, x=%f", x)) DumpVec("vecx", tblTenon.vecx) DumpVec("vecy", tblTenon.vecy) DumpVec("vecz", tblTenon.vecz) ]] tblTenon.opp_side=1 + ((nSide +1) % 4) -- side originally 1...4 tblTenon.opp_plane = GetLocalTenonSide(tblTenon.opp_side) --toLog(string.format("oppside for %d=%d", nSide, tblTenon.opp_side)) return tblTenon end -- nType ETypeXXX: 1=tenon, 2=dt, 3=mortise, 4=dt-pocket function BtlAddBegEndTenonMortNew( isBeg, nAngleTopDeg, nAngleSideDeg, x, y, z, nSide, nType, nRotAngleDeg, tenonLen, tenonWidth, tenonHeight, roundingRadius, nAngleTenonDeg ) local nBtlSide, bMirrorY, tblInfo, tblTenon, t, pa, pb, pc, pd, b, d1, p14, p15, nSideOrg nSideOrg=nSide if gnBtlLogDir==EBtlDirEnd then error( "BAD DIR" ) --nAngle=PI-nAngle end --toLog(string.format("isBeg=%s", tostring(isBeg))) if isBeg==false then x=gnCurrTotLen-x end tblTenon=CalcTenonGeo( isBeg, nAngleTopDeg, nAngleSideDeg, x, y, z, nSide, nRotAngleDeg ) -- Calculates x,y,z into plank coordinates (originally y was offset from the middle) -- Calc origin on the opposite plane (which will be the anchor for btl tenon) and tenon's z-vector's intersection t=tblTenon.opp_plane pa,pb,pc,pd = MakePlaneNormal(t.vecz.x, t.vecz.y, t.vecz.z, t.x, t.y, t.z) b,x,y,z,d1=LinePlaneX(pa, pb, pc, pd, tblTenon.x, tblTenon.y, tblTenon.z, tblTenon.x+tblTenon.vecz.x, tblTenon.y+tblTenon.vecz.y, tblTenon.z+tblTenon.vecz.z) if not b then AddCncErr(tblTenon.x, "ERROR: TENON SKIPPED, COULD NOT CALCULATE ANCHOR POINT ON OPPOSITE SURFACE, TRY CHANGING THE TENON ANCHOR SIDE") return end if nSide==2 or nSide==4 then -- We calculated in plank's coords y,z=z,y end nSide=tblTenon.opp_side p14=d1-tenonHeight --toLog(string.format("nSide=%d x=%f y=%f z=%f d1=%f p14=%f", nSide, x, y, z, d1, p14)) -- Calc distance from original origin to the bottom of the original anchor surface t=tblTenon.plane pa,pb,pc,pd = MakePlaneNormal(t.vecz.x, t.vecz.y, t.vecz.z, t.x, t.y, t.z) b,b,b,b,d1=LinePlaneX(pa, pb, pc, pd, tblTenon.x, tblTenon.y, tblTenon.z, tblTenon.x+tblTenon.vecz.x, tblTenon.y+tblTenon.vecz.y, tblTenon.z+tblTenon.vecz.z) if not b then AddCncErr(tblTenon.x, "ERROR: TENON SKIPPED, COULD NOT CALCULATE ANCHOR POINT ON SURFACE, TRY CHANGING THE TENON ANCHOR SIDE") return end p15=-d1 --toLog(string.format("d1=%f p15=%f", nSide, d1, p15)) --[[ if true then AddCncErr(x, "ERROR: TENON NOT SUPPORTED") return end ]] local nBegEnd, a -- Anchor x=BtlGetXc(x) nBtlSide,bMirrorY=BtlGetSide14(nSide) nBegEnd=2 if isBeg==false then nBegEnd=1 end -- Tenon bottom mid faceDy=gnCurrWidth if nSide==2 or nSide==4 then faceDy=gnProfHeight end -- Rounding: Pass the value used in AF to the machine if roundingRadius180 then a=a-360 end if a<-90 or a>90 then AddCncErr(x, string.format("ERROR: TENON SKIPPED, BTL TENON ROTATION IS MAX +-90 DEG, IS CURRENTLY %0.1f, TRY CHANGING THE TENON ANCHOR SIDE", a)) return end a=a+90 -- To 0...180 a=180-a -- Assume rounding 22.5 mm is the default one machine uses (AF default rounding r) local p04, p10 p04=0 p10=0 if math.abs(roundingRadius)180 then a=a-360 end if a<-90 or a>90 then AddCncErr(x, string.format("ERROR: TENON SKIPPED, BTL TENON ROTATION IS MAX +-90 DEG, IS CURRENTLY %0.1f, TRY CHANGING THE TENON ANCHOR SIDE", a)) return end a=a+90 -- To 0...180 a=180-a d1=2*roundingRadius-tenonWidth -- Straight part if d14 then return false end -- Must be tilted and lengthwise IS supported if math.abs(groAngle)PI/180 then return false end local tblGroInfo, tblGro tblGro={} tblGro.guid = gsCurrentLogGuid tblGro.x1 = x1 tblGro.y1 = y1 tblGro.x2 = x2 tblGro.y2 = y2 tblGro.depth = groDepth tblGro.width = groWidth tblGro.groAngleRad = groAngle tblGro.face = groFace tblGro.groTiltAngleRad = groAngleLen if y1>y2 then -- To have positive angle af_request("mc_swapgroref", tblGro) x1 = tblGro.x1 y1 = tblGro.y1 x2 = tblGro.x2 y2 = tblGro.y2 groDepth = tblGro.depth groAngle = tblGro.groAngleRad groAngleLen = tblGro.groTiltAngleRad end tblGroInfo = af_request("mc_getgroinfo", tblGro) if tblGroInfo==nil or tblGroInfo.isBird==false then return false end -- On surface, z neg=inside the plank dxDir=(x2-x1) dyDir=(y2-y1) dzDir=0 dxDir,dyDir,dzDir,d=ToUnitVec3(dxDir,dyDir,0) if d==false then return false end -- To left from surface dxBot,dyBot,dzBot=RotateSingle3D(-dyDir, dxDir, 0, dxDir, dyDir, 0, groAngle) dxDir,dyDir,dzDir=RotateSingle3D(dxDir, dyDir, 0, dxBot,dyBot,dzBot, groAngleLen) dxDown,dyDown,dzDown=CalcCross3D(dxBot, dyBot, dzBot, dxDir, dyDir, dzDir) --ac_msgbox(string.format("dxDir=%f, dyDir=%f, dzDir=%f, dxDown=%f, dyDown=%f, dzDown=%f, dxBot=%f, dyBot=%f, dzBot=%f", dxDir, dyDir, dzDir, dxDown, dyDown, dzDown, dxBot, dyBot, dzBot)) -- Calculate groove bottom local angleBeg, angleEnd local xBot1, yBot1, zBot1, xBot2, yBot2, zBot2 local nLeft, begZ, endZ xBot1=x1+dxDown*groDepth yBot1=y1+dyDown*groDepth zBot1=0-dzDown*groDepth xBot2=xBot1+dxBot*groWidth yBot2=yBot1+dyBot*groWidth zBot2=zBot1-dzBot*groWidth --AddCncErr(0, string.format("xBot1=%f yBot1=%f zBot1=%f xBot2=%f yBot2=%f zBot2=%f", xBot1, yBot1, zBot1, xBot2, yBot2, zBot2)) --AddCncErr( x1, string.format("x1=%f, y1=%f, x2=%f, y2=%f, groDepth=%f, groWidth=%f, groAngle=%f, groAngleLen=%f", x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen) ) -- Calc side projection angles (valid angle for birdmouth 1...89 deg) nBegEnd=4 if tblGroInfo.birdBegCutFace==2 then angleBeg=math.atan2(dzBot, -dxBot) -- Positive if cutting the wood (should be always). This is on plank side surface - not on the groove. xBot=xBot1 yBot=yBot1 zBot=zBot1 else angleBeg=math.atan2(-dzDown, dxDown) xBot=xBot2 yBot=yBot2 zBot=zBot2 end if tblGroInfo.birdEndCutFace==2 then angleEnd=math.atan2(-dzBot, -dxBot) -- Positive if cutting the wood (should be always) else angleEnd=math.atan2(-dzDown, -dxDown) end if angleBegPI*89/180 or angleEndPI*89/180 then -- Can be v-cut return false end if groAngle>0 then nAngle7=PI2-groAngle nAngle8=nAngle7+PI2 else nAngle7=-groAngle nAngle8=nAngle7+PI2 end if nAngle7PI*89/180 or nAngle8PI-PI/180 then -- Prev check should disable this AddCncErr( x1, string.format("BTL ERROR: FAILED TO CONVERT GROOVE TO BIRDS MOUTH - SAVED AS GROOVE (nAngle7=%f, nAngle8=%f, groAngle=%f)", nAngle7, nAngle8, groAngle) ) return false end nAngle8=PI-angleBeg nAngle7=nAngle8-PI2 -- Ok bird's mouth and bottom beg calculated local nBtlSide, bMirrorY local nGroLen, nAngle, dx, dy dx=x2-x1 dy=y2-y1 nGroLen=math.sqrt(dx*dx+dy*dy)*math.cos(groAngleLen) -- ON GROOVE BOTTOM nAngle=math.atan2(dyDir, dxDir) if nBegEnd==4 then nAngle=PI-nAngle end ------------------------------------- -- Adjust to keep inside the plank. dyDir is always positive local xoff, yoff, zoff if yBot<0 and dyDir>0.001 then yoff=-yBot yBot=0 xoff=dxDir/dyDir*yoff xBot=xBot+xoff zoff=dzDir/dyDir*yoff zBot=zBot-zoff nGroLen=nGroLen-math.sqrt(xoff*xoff+yoff*yoff+zoff*zoff) end faceDy=gnCurrWidth if groFace==2 or groFace==4 then faceDy=gnProfHeight end yoff=yBot+nGroLen*dyDir-faceDy if yoff>0 then xoff=dxDir/dyDir*yoff zoff=dzDir/dyDir*yoff nGroLen=nGroLen-math.sqrt(xoff*xoff+yoff*yoff+zoff*zoff) end -- Adjust to keep inside the plank. ------------------------------------- if nAnglePI-PI/180 then return false end nBtlSide,bMirrorY=BtlGetSide14(groFace) x=BtlGetXc(xBot) y=yBot yBot2=nGroLen*dyDir -- Actually len on surface --ac_msgbox(string.format("x=%f, y=%f, zBot=%f, yBot2=%f, bMirrorY=%s, nGroLen=%f", x, y, zBot, yBot2, tostring(bMirrorY), nGroLen)) z=-dzDir if groFace==3 or groFace==4 then -- Swap beg/end points x=BtlGetXc(xBot+nGroLen*dxDir) y=yBot+nGroLen*dyDir zBot=zBot-nGroLen*dzDir nAngle=PI-nAngle z=-z end if bMirrorY then y=BtlMirrorYc(y, groFace) end --AddCncErr( x1, string.format("dzDir=%f z=%f, bMirrorY=%s)", dzDir, z, tostring(bMirrorY)) ) --ac_msgbox( string.format("2: x1=%f, y1=%f, x2=%f, y2=%f, groDepth=%f, groWidth=%f, groAngle=%f, groAngleLen=%f", x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen) ) s = string.format( "PROCESSKEY: %d-020-%d BIRDS MOUTH\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P05:00000000 P06:%s P07:%s P08:%s P09:00000000 P10:00000000 P11:%s P12:%s P13:%s P14:00000000 P15:00000000", nBegEnd, -- X-020-1 nBtlSide, -- 3-020-X BtlLen2Str(x), -- P01 BtlLen2Str(y), -- P02 BtlNum2Str(nAngle / PI * 180), -- P06 BtlNum2Str(nAngle7 / PI * 180), -- P07 BtlNum2Str(nAngle8/ PI * 180), -- P08 BtlLen2Str(zBot), -- P11 BtlLen2Str(zBot + nGroLen*z), -- P12 BtlLen2Str(yBot2) -- P13 ) BtlAddMc( x, s ) -- Add also marking lines to the bottom side BtlAddBirdMark( tblGroInfo.projside1, sPieceNum ) BtlAddBirdMark( tblGroInfo.projside2, sPieceNum ) return true end function BtlAddBirdMark( tblProj, sPieceNum ) --DumpTbl( tblProj ) if tblProj.linebot.len<0.001 then --If not cutting whole plank: AddCncErr( 0, "WARNING: BtlAddBirdMark CANNOT ADD MARKER LINE1" ) return end if not IsIBeam() or not HasReinforce() then return end local line2, mulDir2, x, y, dy, faceHeight -- Which one make up the birdsmouth (and other line positive towards the original surface line2=tblProj.lineref mulDir2=1 x=tblProj.lineref.x1 y=tblProj.lineref.y1 if tblProj.lineref.len0 then dy=0 if y4 then if math.abs(x2-x1)>math.abs(y1-y2) then if x1>x2 then bSwapLR=true end elseif y1>y2 then bSwapLR=true end elseif y1>y2 then bSwapLR=true end if bSwapLR then --AddCncErr( x1, "y1>y2" ) af_request("mc_swapgroref", tblGro) x1 = tblGro.x1 y1 = tblGro.y1 x2 = tblGro.x2 y2 = tblGro.y2 groDepth = tblGro.depth groAngle = tblGro.groAngleRad groAngleLen = tblGro.groTiltAngleRad end tblGroInfo = af_request("mc_getgroinfo", tblGro) if tblGroInfo==nil then return false end if tblGroInfo.vHun~=1 then return false end -- To ends try always, for other sides use only if double angle if groFace<5 and (math.abs(groAngle)0.0099 or groFace>=5 or groAngleLen>EPS then return false end local nBtlSide,bMirrorY, len, dx, dy, nAngle, s x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroCalcMid(x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen) nBtlSide,bMirrorY=BtlGetSide14(groFace) dx=x2-x1 dy=y2-y1 len=math.sqrt(dx*dx+dy*dy) --ac_environment("tolog", string.format("dx=%f, dy=%f, x1=%f, y1=%f, x2=%f, y2=%f", dx, dy, x1, y1, x2, y2)) if bMirrorY then y1=BtlMirrorYc(y1, groFace) dy=-dy groAngle=-groAngle groAngleLen=-groAngleLen -- Not supported now end nAngle=math.atan2( dy, dx ) nAngle=PI-nAngle if nAngle>PI then nAngle=-2*PI+nAngle elseif nAngle<-PI then nAngle=2*PI+nAngle end x1=BtlGetXc(x1) s = string.format( "PROCESSKEY: %d-013-%d SAW CUT\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P06:%s P07:%s P08:%s P11:%s P12:%s", 0, -- X-013-1 nBtlSide, -- 1-013-X BtlLen2Str(x1), -- P01 BtlLen2Str(y1), -- P02 BtlNum2Str(nAngle / PI * 180), -- P06 BtlNum2Str((PI2 - groAngle) / PI * 180), -- P07 BtlNum2Str(groAngleLen / PI * 180), -- P08 (anglelen) BtlLen2Str(groDepth), -- P11 BtlLen2Str(len) -- P12 ) BtlAddMc( x1, s ) return true end -- Simple rectangular groove -- Cuts completely trough the target, currently only from the front -- Rectangle is extended by length to the right of line p1->p2 function BtlAddRectangularGro(sPieceNum, p1, p2, length) local cutFace=2 local nBtlSide,bMirrorY=BtlGetSide14(cutFace) p1.x=BtlGetXc(p1.x) p2.x=BtlGetXc(p2.x) if bMirrorY then p1.y=BtlMirrorYc(p1.y, cutFace) p2.y=BtlMirrorYc(p2.y, cutFace) end local v1=Sub2D(p2, p1) local p3=Add2D(p2, Mul2D(GetUnitVecRight(v1), length)) local p4=Add2D(p3, Mul2D(v1, -1)) -- Calculate which point is bottom right and use that as the origin -- since groove angle has to be 0topRight.y then bottomRight, topRight=topRight, bottomRight end local vec=Sub2D(topRight, bottomRight) s = string.format( "PROCESSKEY: 3-030-%d GROOVE\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P04:%s P06:%s P07:%s P08:%s P09:%s P10:00000000 P11:%s P12:%s P13:00000000 P14:%s", nBtlSide, -- 3-030-X BtlLen2Str(bottomRight.x), -- P01 BtlLen2Str(bottomRight.y), -- P02 BtlNum2Str(32), -- P04 BtlNum2Str(math.atan2(vec.y, vec.x)/PI*180), -- P06 BtlNum2Str(90.0), -- P07 BtlNum2Str(0.0), -- P08 BtlNum2Str(90.0), -- P09 BtlLen2Str(gnCurrWidth), -- P11 BtlLen2Str(Distance2D(points[1], points[3])), -- P12 BtlLen2Str(math.abs(points[3].y-points[4].y)) -- P14 ) BtlAddMc(bottomRight.x, s) end function BtlDirToAngles(dirUpX, dirUpY, dirUpZ) local rotZ=0.0 if math.abs(dirUpX)>EPS or math.abs(dirUpY)>EPS then rotZ=math.atan2(dirUpY, -dirUpX) if rotZ<0.0 then rotZ=PI*2+rotZ end end return rotZ, PI-math.asin(dirUpZ) end function BtlAddGroAsDrilling(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) if groFace==5 or groFace==6 then local dx=x2-x1 local dy=y2-y1 local length=math.sqrt(dx*dx+dy*dy) local ux=dx/length local uy=dy/length local x=x1+ux*length/2-uy*groWidth/2 local y=y1+uy*length/2+ux*groWidth/2 local diameter=TernaryIf(lengthgnCurrTotLen+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 BTL 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 ) ) orgX1=x1 orgY1=y1 orgX2=x2 orgY2=y2 -- Unit vector for the groove if gnBtlYoffset~=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-gnBtlYoffset y2=y2-gnBtlYoffset end end if groType==8 then BtlAddGroAsDrilling(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) return end -- Try birds mouth if BtlAddGroAsBird(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) then return end -- As V-cut at the end if BtlAddGroAsVCut(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) then return end -- If it is a narrow groove if BtlAddGroAsSawCut(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen) then return end if groFace>4 then local ngroDepth, nBegEnd, pos -- Beg&End, adjust to midline, 017 takes groove middle point as refence x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroCalcMid(x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen) if groFace==5 then pos=BtlGetXc(0) else pos=BtlGetXc(gnCurrTotLen) end dx=x2-x1 dy=y2-y1 nGroLen = math.sqrt( dx*dx + dy*dy ) -- Tämä on uran pituus pinnassa, pohjalla voi olla pienempi nBegEnd=4 if groFace==6 then nBegEnd=3 end if math.abs(x2-x1)>0.0001 then -- Non-vertical groove if math.abs(groAngle)>PI/180 then --ac_environment("tolog", string.format("x1=%f y1=%f x2=%f y2=%f\n", x1, y1, x2, y2) ) if x1<0.001 and x2>gnCurrWidth-0.001 and math.abs(groAngleLen)PI/180 then AddCncErr( pos, string.format("ERROR: LENGTHWISE TILTED GROOVE NOT VERTICAL - ANGLE RESETED") ) groAngleLen=0 end end -- Depth at middle point groDepth = groDepth + nGroLen * 0.5 * math.sin(groAngleLen) nBtlSide=BtlGetSide14(3) -- Anchor to bottom side nBegEnd=4 if groFace==6 then nBegEnd=3 end nGroLen=nGroLen * math.cos(groAngleLen) -- Len at bottom nAngle=-math.atan2(dy, dx) if nAngle<0 then nAngle=2*PI+nAngle end if nAngle>PI then -- Change from 180...360 deg to 0...180 nAngle=nAngle-PI end if (nBtlSide==1 or nBtlSide==3) and math.abs(groAngle)PI then -- Change from 180...360 deg to 0...180 nAngle=nAngle-PI end end if math.abs(nAngle)gnCurrWidth then -- groDepth=0 -- end local tblGroInfo, tblGro, distFromSurf, tblGroSideIn, nGroSideIn -- Get groove info now for sides outside the plank tblGro={} tblGro.guid = gsCurrentLogGuid tblGro.x1 = x1 tblGro.y1 = y1 tblGro.x2 = x2 tblGro.y2 = y2 tblGro.depth = groDepth tblGro.width = groWidth tblGro.groAngleRad = groAngle tblGro.face = groFace tblGro.groTiltAngleRad = groAngleLen tblGro.vCutToCurrent=1 tblGroInfo = af_request("mc_getgroinfo", tblGro) -- Table ccw dir tblGroSideIn={} tblGroSideIn[1]=tblGroInfo.begIn tblGroSideIn[2]=tblGroInfo.refIn tblGroSideIn[3]=tblGroInfo.endIn tblGroSideIn[4]=tblGroInfo.leftIn nGroSideIn=1 -- Start from beg nBtlSide,bMirrorY=BtlGetSide14(groFace) x1=BtlGetXc(x1) x2=BtlGetXc(x2) if bMirrorY then y1=BtlMirrorYc(y1, groFace) y2=BtlMirrorYc(y2, groFace) end dx=x2-x1 dy=y2-y1 nGroLen=math.sqrt(dx*dx+dy*dy) nAngle=math.atan2(dy, dx) --toLog( string.format("nAngle=%f", nAngle)) if nAnglePI-PI/180.0 then -- Limited to 1...179 deg if recCount==nil then -- First try swapping dir 180 deg recCount=0 x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroRotateToNext(orgX1, orgY1, orgX2, orgY2, groDepth, groWidth, groAngle, groAngleLen, groFace) x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroRotateToNext(x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen, groFace) BtlAddGro(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen, groType, recCount) return else if recCount==4 then AddCncErr( x1, string.format("ERROR: BTL CNC WRITER COULD NOT SWAP GROOVE DIR - MAY CAUSE ERRORS LATER (2)") ) else x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroRotateToNext(orgX1, orgY1, orgX2, orgY2, groDepth, groWidth, groAngle, groAngleLen, groFace) BtlAddGro(sPieceNum, x1, y1, x2, y2, groDepth, groWidth, groAngle, groFace, groAngleLen, groType, recCount+1) return end end end --DumpTbl(tblGroInfo.vecDownSurf) --AddCncErr( x1, string.format("distFromSurf=%f", distFromSurf) ) if groFace==3 or groFace==4 then -- Need to swap the ref side x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroRotateToNext(orgX1, orgY1, orgX2, orgY2, groDepth, groWidth, groAngle, groAngleLen, groFace) x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroRotateToNext(x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen, groFace) x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen = BtlGroSwapDir(x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen) x1=BtlGetXc(x1) x2=BtlGetXc(x2) if bMirrorY then y1=BtlMirrorYc(y1, groFace) y2=BtlMirrorYc(y2, groFace) end nGroSideIn=3 -- Start from end end if math.abs(groAngle)>PI/1800 and math.abs(groAngleLen)>PI/1800 then AddCncErr( x1, string.format("ERROR: DOUBLE ANGLED GROOVE - NOT SUPPORTED AND SKIPPED ***") ) return end local tblGro2, tblGroInfo2, tblGroInfo3 tblGro2={} tblGro2.guid = gsCurrentLogGuid tblGro2.x1 = x1 tblGro2.y1 = y1 tblGro2.x2 = x2 tblGro2.y2 = y2 tblGro2.depth = groDepth tblGro2.width = groWidth tblGro2.groAngleRad = groAngle tblGro2.face = groFace tblGro2.groTiltAngleRad = groAngleLen tblGro2.vCutToCurrent=1 tblGroInfo2 = af_request("mc_getgroinfo", tblGro2) distFromSurf=-groDepth*tblGroInfo2.vecDownSurf.z -- Positive inside the plank tblGro2.groAngleRad = 0 tblGroInfo3 = af_request("mc_getgroinfo", tblGro2) x1=x1+groDepth*tblGroInfo3.vecDownSurf.x -- Projected to surface (only lengthwise) y1=y1+groDepth*tblGroInfo3.vecDownSurf.y --toLog( string.format("2: x1=%f, y1=%f, x2=%f, y2=%f, groDepth=%f, groWidth=%f, groAngle=%f, groAngleLen=%f", x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen) ) dx=x2-x1 dy=y2-y1 nGroLen=math.sqrt(dx*dx+dy*dy) -- dx and dy unused after this -- # P4: which sides are open/not inside the plank p4=32 -- Change 9/2019: Mark it as open upwards: 0 -- Begin if tblGroSideIn[nGroSideIn]==0 then p4=p4+4 end nGroSideIn=(nGroSideIn%4)+1 -- Ref if tblGroSideIn[nGroSideIn]==0 then p4=p4+2 end nGroSideIn=(nGroSideIn%4)+1 -- End if tblGroSideIn[nGroSideIn]==0 then p4=p4+8 end nGroSideIn=(nGroSideIn%4)+1 -- Left if tblGroSideIn[nGroSideIn]==0 then p4=p4+1 end local faceDy faceDy=gnProfHeight if groFace==1 or groFace==3 then faceDy=gnCurrWidth end if math.abs(y1)<0.0001 then y1=0 end if math.abs(y1-faceDy)<0.0001 then y1=faceDy end --toLog( string.format("2: x1=%f, y1=%f, x2=%f, y2=%f, groDepth=%f, groWidth=%f, groAngle=%f, groAngleLen=%f", x1, y1, x2, y2, groDepth, groWidth, groAngle, groAngleLen) ) if math.abs(tblGroInfo2.vecLenSurf.y)>0.010 then if tblGroInfo.begIn==0 then -- Move y1 to zero local y y=-y1 if tblGroInfo2.vecLenSurf.y<0 then y=-y end x1=x1 + y * tblGroInfo2.vecLenSurf.x/tblGroInfo2.vecLenSurf.y distFromSurf=distFromSurf - y * tblGroInfo2.vecLenSurf.z/tblGroInfo2.vecLenSurf.y y1=0 end if tblGroInfo.endIn==0 and y2>y1 and y24 then AddCncErr( 0, string.format("WARNING - MARKING AT BAD SIDE (%d) SKIPPED", groFace) ) return end if not bLine and strText=="" then return end -- Second thought 12/2014: Let those come: --if bIsStop and groFace~=4 then -- Machine cannot print to these sides -- return --end nBtlSide,bMirrorY=BtlGetSide14(groFace) x1=BtlGetXc(x1) x2=BtlGetXc(x2) if bMirrorY then y1=BtlMirrorYc(y1, groFace) y2=BtlMirrorYc(y2, groFace) end if y1>y2+0.0001 then y1,y2=y2,y1 x1,x2=x2,x2 end -- Max height for the text if groFace==1 or groFace==3 then dy=gnCurrWidth else dy=gnProfHeight-gnBtlYoffset end fontSize=fontSize/100 -- From cm to meter if fontSize>dy*0.8 then fontSize=dy*0.8 -- Machine will determine the height end -- Build bits bitMask=1 if bLine then bitMask=0 end if strText~="" then if yPosText==1 then -- bottom bitMask=bitMask+2 elseif yPosText==3 then -- Top bitMask=bitMask+8 else -- Mid bitMask=bitMask+4 end if xPosText==1 then -- left bitMask=bitMask+16 elseif xPosText==3 then -- right bitMask=bitMask+64 else -- Mid bitMask=bitMask+32 end bitMask=bitMask+128 end nAngle=math.atan2(y2-y1,x2-x1)/PI*180.0 -- In degrees if nAngle>179 and nAngle<181 then -- In plank dir x2-1.1 then -- In plank dir x2>x1 if bLine then s = string.format( "PROCESSKEY: 4-060-%d MARKING\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P04:%s P06:%s P07:%s P11:%s P12:%s P13:%s", nBtlSide, -- 4-040-X BtlLen2Str(x1), -- P01 BtlLen2Str(y1), -- P02 BtlNum2Str(bitMask), -- P04 BtlNum2Str(90), -- P06 BtlNum2Str(90), -- P07 BtlLen2Str(x2-x1), -- P11 BtlLen2Str(0.001), -- P12, 1mm BtlLen2Str(fontSize) -- P13 ) BtlAddMc( x1, s ) end if strText=="" then return end -- Adjust params to have line 90 deg at middle y1=dy*0.5-0.0005 y2=y1+0.001 x1=(x1+x2)*0.5 x2=x1 nAngle=90 --DOES NOT WORK: bitMask=bitMask+1 -- No line nTextParallel=0 -- Will be anyway end if nAngle>80 and nAngle<100 and nTextParallel==1 then -- Turn text and recalc bitmask bitMask=BitTest(bitMask,0) -- Keep line on/off -- xPos and yPos swapped if xPosText==1 then bitMask=bitMask+2 elseif xPosText==3 then bitMask=bitMask+8 else bitMask=bitMask+4 end if yPosText==3 then bitMask=bitMask+16 elseif yPosText==1 then bitMask=bitMask+64 else bitMask=bitMask+32 end bitMask=bitMask+1024 end -- Add text always with this s = string.format( "PROCESSKEY: 4-060-%d MARKING\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P04:%s P06:%s P07:00000000 P11:00000000 P12:%s P13:%s", nBtlSide, -- 4-040-X BtlLen2Str(x1), -- P01 BtlLen2Str(y1), -- P02 BtlNum2Str(bitMask), -- P04 BtlNum2Str(nAngle), -- P06 BtlLen2Str(y2-y1), -- P12 BtlLen2Str(fontSize) ) if strText~="" then s=s .. string.format(" P15:\"%s\"", string.gsub(strText, "\"", "")) end BtlAddMc( x1, s ) end function TernaryIf ( cond , T , F ) if cond then return T else return F end end function BtlAddVCutUsingSawCuts(nBtlSide, nSide, isBeg, nPos, y, p06, p08) cutDepth = TernaryIf(nSide==1 or nSide==3, gnProfHeight, gnCurrWidth) cutLen = gnCurrTotLen -- TODO: Check if this is OK or do we need to calculate exact length to edge p06 = -p06 if not isBeg then p06 = TernaryIf(p06 > 0, 180 - p06, -(180 + p06)) p08 = TernaryIf(p08 > 0, 180 - p08, -(180 + p08)) end leftOrientation = 4 -- Begin oriented rightOrientation = 3 -- End oriented if p08 > p06 then leftOrientation, rightOrientation=rightOrientation, leftOrientation end s = string.format( "PROCESSKEY: %d-013-%d SAW CUT\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P06:%s P07:%s P08:%s P11:%s P12:%s", leftOrientation, -- X-013-1 nBtlSide, -- 1-013-X BtlLen2Str(nPos), -- P01 BtlLen2Str(y), -- P02 BtlNum2Str(p06), -- P06 BtlNum2Str(90.0), -- P07 BtlNum2Str(0.0), -- P08 BtlLen2Str(cutDepth), -- P11 BtlLen2Str(cutLen) -- P12 ) BtlAddMc( nPos, s ) s = string.format( "PROCESSKEY: %d-013-%d SAW CUT\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P06:%s P07:%s P08:%s P11:%s P12:%s", rightOrientation, -- X-013-1 nBtlSide, -- 1-013-X BtlLen2Str(nPos), -- P01 BtlLen2Str(y), -- P02 BtlNum2Str(p08), -- P06 BtlNum2Str(90.0), -- P07 BtlNum2Str(0.0), -- P08 BtlLen2Str(cutDepth), -- P11 BtlLen2Str(cutLen) -- P12 ) BtlAddMc( nPos, s ) end -- In ArchiFrame, angle tilt comes before bevel tilting function BtlAddBegEndVCut( sPieceNum, isBeg, nSide, x, y, angleDegR, bevelDegR, angleDegL, bevelDegL ) local nBtlSide, nPos, bMirrorY, height, b, endMul, nBeg, s, p06, p08 nBtlSide,bMirrorY=BtlGetSide14(nSide) b=bMirrorY if isBeg then b=not b end if nSide==3 or nSide==4 then b=not b end if b then p06=bevelDegL p08=bevelDegR else --AF and BTL think the same way here: y=BtlMirrorYc(y, nSide) p06=bevelDegR p08=bevelDegL end -- Set p06 and p08 if isBeg then nPos=x endMul=1 nBeg=2 else nPos=gnCurrTotLen-x endMul=-1 nBeg=1 end if math.abs(angleDegR-90)>0.1 or math.abs(angleDegL-90)>0.1 then AddCncErr( x, "WARNING: V-CUT, ONLY 90 DEG ANGLE SUPPORTED, ANGLE SAVED AS 90 DEG." ) end if nSide==1 or nSide==3 then y=gnCurrWidth*0.5-endMul*y else y=gnProfHeight*0.5-endMul*y end nPos = BtlGetXc(nPos) if p06 <= 1.0 or p08 <= 1.0 then BtlAddVCutUsingSawCuts(nBtlSide, nSide, b, nPos, y, p06, p08) return end s = string.format( "PROCESSKEY: %d-011-%d DOUBLE CUT\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P05:00000000 P06:%s P07:%s P08:%s P09:%s", nBeg, -- X-011-1 nBtlSide, -- 1-011-X BtlLen2Str(nPos), -- P01 BtlLen2Str(y), -- P02 BtlNum2Str(p06), -- P06 BtlNum2Str(angleDegR), -- P07 BtlNum2Str(180.0-p08), -- P08 BtlNum2Str(angleDegL) -- P09 ) BtlAddMc( nPos, s ) end function BtlAddHiddenShoe( isBeg, sPieceNum, holeWidth, holeHeight, holeDepth, groDepth, groWidth, drillLowest, drillFromEnd, drillSize, drillSpacing, drillCount ) AddCncErr(0, string.format("BTL: HIDDEN SHOE NOT SUPPORTED") ) end -- Kun palkki menee kapuloiden läpi function BtlAddBalkMid(sPieceNum, midPos, width, depthTop, depthBot, depthSides) local xc xc=midPos + width * 0.5 BtlAddGro(sPieceNum, xc, 0, xc, gnCurrWidth, depthTop, width, 0, 1, 0) -- Top BtlAddGro(sPieceNum, xc, 0, xc, gnProfHeight, depthSides, width, 0, 2, 0) -- Front BtlAddGro(sPieceNum, xc, 0, xc, gnCurrWidth, depthBot, width, 0, 3, 0) -- Bottom BtlAddGro(sPieceNum, xc, 0, xc, gnProfHeight, depthSides, width, 0, 4, 0) -- Back end function BtlAddSideJoint( sPieceNum, logFace, bDove, midPos, borderBot, tenonLen, tenonWidth, tenonHeight, angleDove, roundingRadius, rotAngleDeg, housingDepth ) local nBtlSide, bMirrorY, xc, yc, y1, y2, s, minDist, ux, faceHeight, a, s if rotAngleDeg==nil then rotAngleDeg=0 end if housingDepth==nil then housingDepth=0 end if math.abs(housingDepth)>EPS then AddCncErr( midPos, string.format("ERROR: Side joint housing not supported (%f)", housingDepth) ) return end if logFace>4 then -- beg/end AddCncErr( midPos, string.format("ERROR: Side joint to end not supported") ) return end -- To side local tenonHeightOrg=tenonHeight tenonHeight=math.abs(tenonHeight) -- 2/2022: Negative to limit DT-pocket (used by bvn and bvx-writers only) -- btl anchors DT to the top of the pocket: BTL writer uses tenon height directly from AF (no change to negative height) yc=borderBot -- AF keeps at bottom of the DT, BTL at top a=PI*rotAngleDeg/180.0 midPos=midPos-math.sin(a)*tenonHeight yc=yc+math.cos(a)*tenonHeight nBtlSide,bMirrorY=BtlGetSide14(logFace) xc=BtlGetXc(midPos) --AddCncErr( 0, string.format("mirrory=%s", tostring(bMirrorY)) ) faceHeight=gnProfHeight if logFace==1 or logFace==3 then faceHeight=gnCurrWidth end if bMirrorY then yc=faceHeight-yc rotAngleDeg=-rotAngleDeg end if gnBtlLogDir==EBvnDirEnd then rotAngleDeg=-rotAngleDeg end -- DT pocket/Mortise, unlike with Hundegger in btl we always follow the tenon height parameter if math.abs(rotAngleDeg)<0.05 then rotAngleDeg=0 end rotAngleDeg=math.fmod(rotAngleDeg, 360) if rotAngleDeg<-180 then rotAngleDeg=360+rotAngleDeg elseif rotAngleDeg>180 then rotAngleDeg=rotAngleDeg-360 end -- Zero rotation means in AF the bottom of the joint goes from bottom of the surface to top, in btl the same angle is -90 --toLog(string.format("pre: rotAngleDeg=%f", rotAngleDeg)) rotAngleDeg=rotAngleDeg-180 rotAngleDeg=rotAngleDeg+90 if rotAngleDeg<=-180 then rotAngleDeg=360+rotAngleDeg end --toLog(string.format("post1: rotAngleDeg=%f, angleDove=%f", rotAngleDeg, angleDove)) if bMirrorY then if rotAngleDeg<0 then rotAngleDeg=180+rotAngleDeg else rotAngleDeg=rotAngleDeg-180 end end --toLog(string.format("post2: rotAngleDeg=%f, angleDove=%f", rotAngleDeg, angleDove)) if bDove then s = string.format( "PROCESSKEY: 4-055-%d DT POCKET\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P04:00000000 P05:00000000 P06:%s P07:%s P09:00000000 P10:%s P11:%s P12:%s P13:%s P14:00000000 P15:00000000", nBtlSide, -- 4-055-X BtlLen2Str(xc), -- P01 BtlLen2Str(yc), -- P02 BtlNum2Str(rotAngleDeg), -- P06 BtlNum2Str(90), -- P07 BtlNum2Str(angleDove * 0.5), -- P10 BtlLen2Str(tenonLen), -- P11 BtlLen2Str(tenonWidth), -- P12 BtlLen2Str(tenonHeight) -- P13 ) BtlAddMc( xc, s ) return end tenonLen=tenonLen+housingDepth -- Assume rounding 22.5 mm is the default one machine uses (AF default rounding r) local p04, p10 p04=0 p10=0 if math.abs(roundingRadius)4 then AddCncErr( xStart, string.format("BtlAddSaw, BAD FACE: %d. MACHINING SKIPPED", logFace) ) return end nBtlSide,bMirrorY=BtlGetSide14(logFace) -- Miten osuu puuhun x1Real=BtlGetXc(xStart) if dWidth==0 then x2Real=BtlGetXc(gnCurrTotLen) else x2Real=BtlGetXc(xStart+dWidth) end if x1Real>x2Real then x1Real,x2Real = x2Real,x1Real end dyFace=gnProfHeight dzNow=gnCurrWidth if logFace==1 or logFace==3 then dyFace=gnCurrWidth dzNow=gnProfHeight end dyFace2=dyFace*0.5 nSingleCut=0 if dYoffTop<-dyFace2+0.001 then -- Single cut only dAngleY1Deg=0 nSingleCut=2 elseif dYoffTop>dyFace2-0.001 then dAngleY2Deg=0 nSingleCut=1 end -- Tarkistetaan x-koordinaatit yc=dYoffTop+dyFace2 if bMirrorY then yc=dyFace-yc dAngleY1Deg,dAngleY2Deg = dAngleY2Deg,dAngleY1Deg if nSingleCut==1 then nSingleCut=2 elseif nSingleCut==2 then nSingleCut=1 end end nP4=0 nLen=x2Real-x1Real if x1Real<0.001 and x2Real>gnCurrTotLen-0.001 then nLen=0 -- Kaikki nP4=0 elseif x1Real<0.001 then nP4=3 nLen=x2Real elseif x2Real>gnCurrTotLen-0.001 then nP4=5 nLen=gnCurrTotLen-x1Real else nP4=1 end if nSingleCut==0 then -- Valley or hip s = string.format( "PROCESSKEY: 0-012-%d LONGITUDINAL CUT\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P04:%s P07:%s P09:%s P11:%s P12:%s P13:%s P14:%s P15:%s P16:%s", nBtlSide, -- 4-010-X BtlLen2Str(x1Real), -- P01 BtlLen2Str(yc), -- P02 BtlNum2Str(nP4), -- P04 BtlNum2Str(dAngleY1Deg), -- P07 BtlNum2Str(dAngleY2Deg), -- P09 BtlLen2Str(dDepthTop), -- P11 BtlLen2Str(nLen), -- P12 BtlNum2Str(90), -- P13 BtlNum2Str(90), -- P14 BtlNum2Str(90), -- P15 BtlNum2Str(90) -- P16 ) BtlAddMc( x1Real, s ) else -- Need to calculate yc to the point where the cut hits the surface if nSingleCut==1 then nAngleRad=dAngleY1Deg else nAngleRad=-dAngleY2Deg end nAngleRad=nAngleRad*PI/180 if math.abs(nAngleRad)0 then isBeg=false end BtlAddSawStraight(sPieceNum, 4, 0, gnCurrTotLen, 0, pt1.y, isBeg) else local isBeg=true local dX=pt2.x-pt1.x if math.abs(dX)dx then xOff=dx bDone=true end x=xMid-dx*0.5+xOff if math.abs(x)<0.001 then x=0.001 end if math.abs(x-gnCurrTotLen)<0.001 then x=gnCurrTotLen-0.001 end BtlAddMarking(sPieceNum, groFace, x, yMid-dy*0.5, x, yMid+dy*0.5, xPosText, yPosText, fontSize, textOff, "", true, 0, true) xOff=xOff+0.650 end -- "Adidas" stripes BtlAddMarking(sPieceNum, groFace, xMid-dx*0.5, yMid-dy*0.5, xMid+dx*0.5, yMid-dy*0.5, xPosText, yPosText, fontSize, textOff, "", true, 0, true) BtlAddMarking(sPieceNum, groFace, xMid-dx*0.5, yMid, xMid+dx*0.5, yMid, xPosText, yPosText, fontSize, textOff, "", true, 0, true) BtlAddMarking(sPieceNum, groFace, xMid-dx*0.5, yMid+dy*0.5, xMid+dx*0.5, yMid+dy*0.5, xPosText, yPosText, fontSize, textOff, "", true, 0, true) end -- sCol Is the btl colour or nil=default -- Returns side against the plate function WriteBtlPlank(unidPlank, nSimilarCount, tblSimilarPos, sCol) local i, plateSide BtlGetPlankBasicParams(unidPlank) -- BTL (viewer) seems to turn the piece itself if gbBtlCalcFeed then if gnCurrWidth-EPS>gnProfHeight then if gnBtlLogDir==EBtlDirBeg then gnBtlPlateSide=EBtlBackSide else gnBtlPlateSide=EBtlFrontSide end else if gnBtlLogDir==EBtlDirBeg then gnBtlPlateSide=EBtlMale else gnBtlPlateSide=EBtlFemale end end end -- 3/2015: Change feeding direction if there are marking only on unsupported side -- Machine is able to do grooves and markings only to the side which is pointing upwards (back side of AF plank with default feeding dir) gtblGros={} gtblMarkings={} for i=1,6 do gtblGros[i]=0 gtblMarkings[i]=0 end gnBtlBestPrintSide=4 -- Back side plateSide=gnBtlPlateSide BtlWritePlankInt(unidPlank, nSimilarCount, tblSimilarPos, sCol) if false then -- Birkeland hack if gnBtlLogDir==EBtlDirBeg and gnBtlPlateSide==EBtlMale and gtblGros[4]==0 and gtblMarkings[4]==0 and (gtblGros[2]>0 or gtblMarkings[2]>0) then --toLog( gsCurrentLogId ) -- Change front side to be pointing upwards gnBtlPlateSide=EBtlFemale gnBtlBestPrintSide=2 -- Front side BtlWritePlankInt(unidPlank, nSimilarCount, tblSimilarPos, sCol) plateSide=gnBtlPlateSide gnBtlPlateSide=EBtlMale end end return plateSide end -- Creates string containing plank's position and orientation -- strName is either "TRANSFORMATION" or "REFERENCEPLANE" -- Trashes given "#plankinfo" info-parameter -- plateSide gnBtlPlateSide -- Uses gPlankTranMat for transformations -- Also applies gnOrigX,Y,Z function BtlCreatePosStr(strName, info, plateSide) info.begc.x=info.begc.x-gnOrigX info.begc.y=info.begc.y-gnOrigY info.begc.z=info.begc.z-gnOrigZ -- BTL origin at lower left looking at begin if gnBtlLogDir~=EBtlDirBeg then toLog("ERROR: BAD PLANK FEEDING DIR OR ROTATION, POSITION INFORMATION INCORRECT") end if gPlankTranMat then -- Transform coordinate system info.begc.x, info.begc.y, info.begc.z=MatTransformPt( info.begc.x, info.begc.y, info.begc.z, gPlankTranMat ) info.vecx.x, info.vecx.y, info.vecx.z=MatTransformVec( info.vecx.x, info.vecx.y, info.vecx.z, gPlankTranMat ) info.vecy.x, info.vecy.y, info.vecy.z=MatTransformVec( info.vecy.x, info.vecy.y, info.vecy.z, gPlankTranMat ) info.vecz.x, info.vecz.y, info.vecz.z=MatTransformVec( info.vecz.x, info.vecz.y, info.vecz.z, gPlankTranMat ) --toLog( "transformed to:" ) --toLog( string.format("info.begc.x=%f, info.begc.y=%f, info.begc.z=%f", info.begc.x, info.begc.y, info.begc.z) ) --toLog( string.format("info.vecx.x=%f, info.vecx.y=%f, info.vecx.z=%f", info.vecx.x, info.vecx.y, info.vecx.z) ) --toLog( string.format("info.vecy.x=%f, info.vecy.y=%f, info.vecy.z=%f", info.vecy.x, info.vecy.y, info.vecy.z) ) --toLog( string.format("info.vecz.x=%f, info.vecz.y=%f, info.vecz.z=%f", info.vecz.x, info.vecz.y, info.vecz.z) ) end if plateSide==EBtlFemale then -- Easiest case, just offset origin by half width in the y direction info.begc.x=info.begc.x+info.vecy.x*info.width*0.5 info.begc.y=info.begc.y+info.vecy.y*info.width*0.5 info.begc.z=info.begc.z+info.vecy.z*info.width*0.5 elseif plateSide==EBtlMale then -- A bit trickier, need to flip z vector 180 degrees aswell and adjust origin in both directions info.begc.x=info.begc.x-info.vecy.x*info.width*0.5+info.vecz.x*info.height info.begc.y=info.begc.y-info.vecy.y*info.width*0.5+info.vecz.y*info.height info.begc.z=info.begc.z-info.vecy.z*info.width*0.5+info.vecz.z*info.height info.vecz.x=-info.vecz.x info.vecz.y=-info.vecz.y info.vecz.z=-info.vecz.z elseif plateSide==EBtlFrontSide then -- Even more tricky, need to rotate z-vector negative 90 degrees, cannot just "flip" it as in previous case local zx,zy,zz=RotateSingle3D( info.vecz.x, info.vecz.y, info.vecz.z, info.vecx.x, info.vecx.y, info.vecx.z, -PI/2 ) info.vecz.x=zx info.vecz.y=zy info.vecz.z=zz info.begc.x=info.begc.x-info.vecz.x*info.width*0.5 info.begc.y=info.begc.y-info.vecz.y*info.width*0.5 info.begc.z=info.begc.z-info.vecz.z*info.width*0.5 elseif plateSide==EBtlBackSide then -- As in previous case, but rotation positive 90 degrees. -- Even more tricky since we need to offset the origin by the "new" y-vector, -- (meaning the vector perpendicular to the new z-vector and the x-vector) local zx,zy,zz=RotateSingle3D( info.vecz.x, info.vecz.y, info.vecz.z, info.vecx.x, info.vecx.y, info.vecx.z, PI/2 ) info.vecz.x=zx info.vecz.y=zy info.vecz.z=zz local yx, yy, yz=CalcCross3D( info.vecx.x, info.vecx.y, info.vecx.z, info.vecz.x, info.vecz.y, info.vecz.z ) info.begc.x=info.begc.x-info.vecz.x*info.width*0.5-yx*info.height info.begc.y=info.begc.y-info.vecz.y*info.width*0.5-yy*info.height info.begc.z=info.begc.z-info.vecz.z*info.width*0.5-yz*info.height else error( "BtlCreatePosStr: Unknown orientation" ) end return string.format( "%s: OX:%s OY:%s OZ:%s XX:%s XY:%s XZ:%s YX:%s YY:%s YZ:%s", strName, BtlLen2Str(info.begc.x), BtlLen2Str(info.begc.y), BtlLen2Str(info.begc.z), BtlLen2Str(info.vecx.x), BtlLen2Str(info.vecx.y), BtlLen2Str(info.vecx.z), BtlLen2Str(info.vecz.x), BtlLen2Str(info.vecz.y), BtlLen2Str(info.vecz.z) ) end -- Creates string containing element's position and orientation -- strName is either "TRANSFORMATION" or "REFERENCEPLANE" -- Trashes given "#plankinfo" info-parameter -- Uses tranMat for transformations -- Also applies gnOrigX,Y,Z function BtlCreatePosStrElem(strName, info, tranMat) info.begc.x=info.begc.x - gnOrigX - info.width*0.5*info.vecy.x info.begc.y=info.begc.y - gnOrigY - info.width*0.5*info.vecy.y info.begc.z=info.begc.z - gnOrigZ - info.width*0.5*info.vecy.z --toLog( string.format("pre transform elem (%s, width=%f):", strName, info.width) ) --toLog( string.format("info.begc.x=%f, info.begc.y=%f, info.begc.z=%f", info.begc.x, info.begc.y, info.begc.z) ) --toLog( string.format("info.vecx.x=%f, info.vecx.y=%f, info.vecx.z=%f", info.vecx.x, info.vecx.y, info.vecx.z) ) --toLog( string.format("info.vecy.x=%f, info.vecy.y=%f, info.vecy.z=%f", info.vecy.x, info.vecy.y, info.vecy.z) ) --toLog( string.format("info.vecz.x=%f, info.vecz.y=%f, info.vecz.z=%f", info.vecz.x, info.vecz.y, info.vecz.z) ) if tranMat then -- Transform coordinate system info.begc.x, info.begc.y, info.begc.z=MatTransformPt( info.begc.x, info.begc.y, info.begc.z, tranMat ) info.vecx.x, info.vecx.y, info.vecx.z=MatTransformVec( info.vecx.x, info.vecx.y, info.vecx.z, tranMat ) info.vecy.x, info.vecy.y, info.vecy.z=MatTransformVec( info.vecy.x, info.vecy.y, info.vecy.z, tranMat ) info.vecz.x, info.vecz.y, info.vecz.z=MatTransformVec( info.vecz.x, info.vecz.y, info.vecz.z, tranMat ) --toLog( "transformed to:" ) --toLog( string.format("info.begc.x=%f, info.begc.y=%f, info.begc.z=%f", info.begc.x, info.begc.y, info.begc.z) ) --toLog( string.format("info.vecx.x=%f, info.vecx.y=%f, info.vecx.z=%f", info.vecx.x, info.vecx.y, info.vecx.z) ) --toLog( string.format("info.vecy.x=%f, info.vecy.y=%f, info.vecy.z=%f", info.vecy.x, info.vecy.y, info.vecy.z) ) --toLog( string.format("info.vecz.x=%f, info.vecz.y=%f, info.vecz.z=%f", info.vecz.x, info.vecz.y, info.vecz.z) ) end return string.format( "%s: OX:%s OY:%s OZ:%s XX:%s XY:%s XZ:%s YX:%s YY:%s YZ:%s", strName, BtlLen2Str(info.begc.x), BtlLen2Str(info.begc.y), BtlLen2Str(info.begc.z), BtlLen2Str(info.vecx.x), BtlLen2Str(info.vecx.y), BtlLen2Str(info.vecx.z), BtlLen2Str(info.vecy.x), BtlLen2Str(info.vecy.y), BtlLen2Str(info.vecy.z) ) end -- Creates string containing board's position and orientation -- strName is either "TRANSFORMATION" or "REFERENCEPLANE" -- Trashes given "#plankinfo" info-parameter -- Uses tranMat for transformations -- Also applies gnOrigX,Y,Z function BtlCreatePosStrBoard(strName, info, tranMat) info.begc.x=info.begc.x - gnOrigX - info.width*0.5*info.vecy.x info.begc.y=info.begc.y - gnOrigY - info.width*0.5*info.vecy.y info.begc.z=info.begc.z - gnOrigZ - info.width*0.5*info.vecy.z --toLog( "pre transform board:" ) --toLog( string.format("info.begc.x=%f, info.begc.y=%f, info.begc.z=%f", info.begc.x, info.begc.y, info.begc.z) ) --toLog( string.format("info.vecx.x=%f, info.vecx.y=%f, info.vecx.z=%f", info.vecx.x, info.vecx.y, info.vecx.z) ) --toLog( string.format("info.vecy.x=%f, info.vecy.y=%f, info.vecy.z=%f", info.vecy.x, info.vecy.y, info.vecy.z) ) --toLog( string.format("info.vecz.x=%f, info.vecz.y=%f, info.vecz.z=%f", info.vecz.x, info.vecz.y, info.vecz.z) ) if tranMat then -- Transform coordinate system info.begc.x, info.begc.y, info.begc.z=MatTransformPt( info.begc.x, info.begc.y, info.begc.z, tranMat ) info.vecx.x, info.vecx.y, info.vecx.z=MatTransformVec( info.vecx.x, info.vecx.y, info.vecx.z, tranMat ) info.vecy.x, info.vecy.y, info.vecy.z=MatTransformVec( info.vecy.x, info.vecy.y, info.vecy.z, tranMat ) info.vecz.x, info.vecz.y, info.vecz.z=MatTransformVec( info.vecz.x, info.vecz.y, info.vecz.z, tranMat ) end return string.format( "%s: OX:%s OY:%s OZ:%s XX:%s XY:%s XZ:%s YX:%s YY:%s YZ:%s", strName, BtlLen2Str(info.begc.x), BtlLen2Str(info.begc.y), BtlLen2Str(info.begc.z), BtlLen2Str(info.vecz.x), BtlLen2Str(info.vecz.y), BtlLen2Str(info.vecz.z), BtlLen2Str(info.vecx.x), BtlLen2Str(info.vecx.y), BtlLen2Str(info.vecx.z) ) end -- nProcessIdent Next free PROCESSIDENT -- Returns cnc, next free nProcessIdent function BtlGetNailDrillCnc(infoOrig, tranMat, nProcessIdent, toolNumberP14, offFromLayer, tblDrills, bNailPoints) local n, i, nType, hasNail, sCnc, nSide, s, x, y, z nSide=1 sCnc="" n=ac_objectget("iMc" ,-1) for i=1,n do nType=NormalizeType(ac_objectget("iMc", i, 1)) if nType==EMcFrNailGroup then --PROCESSKEY: 0-250-3 NailLine_70 --REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:00010000 XY:00000000 XZ:00000000 YX:00000000 YY:00010000 YZ:00000000 --PROCESSPARAMETERS:P01:00016200 P02:00-47600 P03:00001250 P05:00000000 P06:00000000 P07:00000000 P08:00000000 P09:00007100 P10:00000000 P11:00000000 P12:00000000 P13:00002000 P14:00000500 P15:00015000 --PROCESSIDENT: 70 -- Mc data local x1, y1, x2, y2, z2, cols, rows, anglexRad, angleyRad, spacingx, spacingy, side x1 = ac_objectget("iMc", i, 2) y1 = ac_objectget("iMc", i, 3) cols = ac_objectget("iMc", i, 4) rows = ac_objectget("iMc", i, 5) anglexRad=ac_objectget("iMc", i, 6)*PI/180 angleyRad=ac_objectget("iMc", i, 7)*PI/180 spacingx= ac_objectget("iMc", i, 8) spacingy= ac_objectget("iMc", i, 9) side = ac_objectget("iMc", i, 11) -- Get geometry for current surface info=infoOrig.tblSides[side] local col for col=1,cols do -- Local to the board BEG x = x1 + (col -1) * spacingx * math.cos(anglexRad) y = y1 + (col -1) * spacingx * math.sin(anglexRad) -- Transform to the layer (forget z) x2, y2, z2=MatTransformPt( info.origc.x - gnOrigX + info.vecx.x * x + info.vecy.x * y, info.origc.y - gnOrigY + info.vecx.y * x + info.vecy.y * y, info.origc.z - gnOrigZ + info.vecx.z * x + info.vecy.z * y, tranMat ) s = string.format( "PROCESSKEY: 0-250-%d NAILING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:%s P05:00000000 P06:00000000 P07:00000000 P08:00000000 P09:%s P10:00000000 P11:00000000 P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(x2), BtlLen2Str(z2), BtlLen2Str(offFromLayer), BtlInt2Str(nProcessIdent+1), -- P09 BtlInt2Str(20), -- P13 (was 200 before precision) BtlInt2Str(toolNumberP14), -- P14 BtlLen2Str(spacingy), -- P15 nProcessIdent ) sCnc = sCnc .. s nProcessIdent = nProcessIdent + 1 -- Local to the board END x = x1 + (col -1) * spacingx * math.cos(anglexRad) + (rows - 1) * spacingy * math.cos(angleyRad) y = y1 + (col -1) * spacingx * math.sin(anglexRad) + (rows - 1) * spacingy * math.sin(angleyRad) -- Transform to the layer (forget z) x2, y2, z2=MatTransformPt( info.origc.x - gnOrigX + info.vecx.x * x + info.vecy.x * y, info.origc.y - gnOrigY + info.vecx.y * x + info.vecy.y * y, info.origc.z - gnOrigZ + info.vecx.z * x + info.vecy.z * y, tranMat ) s = string.format( "PROCESSKEY: 0-250-%d NAILING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:%s P05:00000000 P06:00000000 P07:00000000 P08:%s P09:00000000 P10:00000000 P11:00000000 P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(x2), -- P01 BtlLen2Str(z2), -- P02 BtlLen2Str(offFromLayer), -- P04 BtlInt2Str(1), -- P08 BtlInt2Str(20), -- P13 (was 200 before precision) BtlInt2Str(toolNumberP14), -- P14 BtlLen2Str(spacingy), -- P15 nProcessIdent ) sCnc = sCnc .. s nProcessIdent = nProcessIdent + 1 end elseif nType==EMcFrNailLine then --PROCESSKEY: 0-250-3 NailLine_70 --REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:00010000 XY:00000000 XZ:00000000 YX:00000000 YY:00010000 YZ:00000000 --PROCESSPARAMETERS:P01:00016200 P02:00-47600 P03:00001250 P05:00000000 P06:00000000 P07:00000000 P08:00000000 P09:00007100 P10:00000000 P11:00000000 P12:00000000 P13:00002000 P14:00000500 P15:00015000 --PROCESSIDENT: 70 -- 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 info=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=MatTransformPt( info.origc.x - gnOrigX + info.vecx.x * xc + info.vecy.x * yc, info.origc.y - gnOrigY + info.vecx.y * xc + info.vecy.y * yc, info.origc.z - gnOrigZ + info.vecx.z * xc + info.vecy.z * yc, tranMat ) s = string.format( "PROCESSKEY: 0-250-%d NAILING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:%s P05:00000000 P06:00000000 P07:00000000 P08:00000000 P09:%s P10:00000000 P11:00000000 P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(x), -- P01 BtlLen2Str(z), -- P02 BtlLen2Str(offFromLayer), -- P03 BtlInt2Str(nProcessIdent+1),-- P09 BtlInt2Str(20), -- P13 (before precision was 200) BtlInt2Str(toolNumberP14), -- P14 BtlLen2Str(spacing), -- P15 nProcessIdent ) sCnc = sCnc .. s nProcessIdent = nProcessIdent + 1 s = string.format( "PROCESSKEY: 0-250-%d NAILING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:%s P05:00000000 P06:00000000 P07:00000000 P08:%s P09:00000000 P10:00000000 P11:00000000 P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(x), -- P01 BtlLen2Str(z), -- P02 BtlLen2Str(offFromLayer), -- P03 BtlInt2Str(1), -- P08 BtlInt2Str(20), -- P13 (before precision was 200) BtlInt2Str(toolNumberP14), -- P14 BtlLen2Str(spacing), -- P15 nProcessIdent ) sCnc = sCnc .. s nProcessIdent = nProcessIdent + 1 end else -- Nail lines -- Local to the board BEG -- Transform to the layer (forget z) x, y, z=MatTransformPt( info.origc.x - gnOrigX + info.vecx.x * x1 + info.vecy.x * y1, info.origc.y - gnOrigY + info.vecx.y * x1 + info.vecy.y * y1, info.origc.z - gnOrigZ + info.vecx.z * x1 + info.vecy.z * y1, tranMat ) s = string.format( "PROCESSKEY: 0-250-%d NAILING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:%s P05:00000000 P06:00000000 P07:00000000 P08:00000000 P09:%s P10:00000000 P11:00000000 P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(x), -- P01 BtlLen2Str(z), -- P02 BtlLen2Str(offFromLayer), -- P03 BtlInt2Str(nProcessIdent+1), -- P09 BtlInt2Str(20), -- P13 (before precision was 200) BtlInt2Str(toolNumberP14), -- P14 BtlLen2Str(spacing), -- P15 nProcessIdent ) sCnc = sCnc .. s nProcessIdent = nProcessIdent + 1 -- Transform to the layer (forget z) x, y, z=MatTransformPt( info.origc.x - gnOrigX + info.vecx.x * x2 + info.vecy.x * y2, info.origc.y - gnOrigY + info.vecx.y * x2 + info.vecy.y * y2, info.origc.z - gnOrigZ + info.vecx.z * x2 + info.vecy.z * y2, tranMat ) s = string.format( "PROCESSKEY: 0-250-%d NAILING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:%s P05:00000000 P06:00000000 P07:00000000 P08:%s P09:00000000 P10:00000000 P11:00000000 P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(x), -- P01 BtlLen2Str(z), -- P02 BtlLen2Str(offFromLayer), -- P03 BtlInt2Str(1), -- P08 BtlInt2Str(20), -- P13 (before precision was 200) BtlInt2Str(toolNumberP14), -- P14 BtlLen2Str(spacing), -- P15 nProcessIdent ) sCnc = sCnc .. s nProcessIdent = nProcessIdent + 1 end -- Drillings are written elsewhere if gbMachiningsToBoard is true elseif nType==EMcFrDrill and tblDrills~=nil and not gbMachiningsToBoard then -- Just collect to layer coordinates local x, y, 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) if depth>EPS then ac_environment( "tolog", string.format("WARNING: DRILL IN BOARD [%s] HAS DEPTH - WILL BE DRILLED THROUGH", ac_objectget("#id")) ) end if side~=2 and side~=4 then ac_environment( "tolog", string.format("WARNING: DRILL IN BOARD [%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) x2, y2, z2=MatTransformPt( info.origc.x - gnOrigX + info.vecx.x * x + info.vecy.x * y, info.origc.y - gnOrigY + info.vecx.y * x + info.vecy.y * y, info.origc.z - gnOrigZ + info.vecx.z * x + info.vecy.z * y, tranMat ) x=x2 y=z2 local scan, found found=false for scan=1,#tblDrills do if math.abs(tblDrills[scan].x - x)0.1 then AddCncErr( xErr, string.format("ERROR: ROTATED DT WITH DOUBLE ROUNDING NOT SUPPORTED, WRITTEN AS STRAIGHT") ) end if math.abs(nAngleTopDeg-90)>0.1 or math.abs(nAngleSideDeg-90)>0.1 then AddCncErr( xErr, string.format("ERROR: ANGLED DT WITH DOUBLE ROUNDING NOT SUPPORTED, SKIPPING WRITING THE OTHER END") ) else -- Anchor to opposite side nSide=nSide+2 if nSide>4 then nSide=nSide-4 end -- Adjust coordinates dy=gnCurrHeight if nSide==2 or nSide==4 then dy=gnCurrWidth end y=-y z=dy-(z+tenonHeight) BtlAddBegEndTenonMortNew( isBeg, nAngleTopDeg, nAngleSideDeg, x, y, z, nSide, nEndType, 0, tenonLen, tenonWidth, 0, roundingRadius, 0 ) -- tenonHeight 0 here end else BtlAddBegEndTenonMortNew( isBeg, nAngleTopDeg, nAngleSideDeg, x, y, z, nSide, nEndType, nRotAngleDeg, tenonLen, tenonWidth, tenonHeight, roundingRadius, nAngleTenonDeg ) end elseif nType==EMcVCutBeg or nType==EMcVCutEnd then --double dType; // 1, EMcVCutBeg/EMcVCutEnd --double dSideOneBased; // 2, 1-4 --double dX; // 3, distance from end to furthest point (usually zero) --double dY; // 4, Y-coordinate for the face from face mid line. pos to right in watching dir, neg to left --double dAngleDegR; // 5, angle at right side of the ref side --double dBevelDegR; // 6 --double dAngleDegL; // 7, Left side --double dBevelDegL; // 8 -- --// GDL generation set by AF --double dDirX; // 9, mid line vector relative to the plank x --double dDirY; // 10 --double dDirZ; // 11 --double dCutAngleDegR; // 12 Rotation for the cut, 0=no cut, 179=max --double dCutAngleDegL; // 13 -- local isBeg, nSide, angleDegR, bevelDegR, angleDegL, bevelDegL if nType==EMcVCutBeg then --ginfo.bBegCut=true isBeg=true else --ginfo.bEndCut=true isBeg=false end nSide =ac_objectget("iMc", i, 2) x =ac_objectget("iMc", i, 3) y =ac_objectget("iMc", i, 4) angleDegR =ac_objectget("iMc", i, 5) bevelDegR =ac_objectget("iMc", i, 6) angleDegL =ac_objectget("iMc", i, 7) bevelDegL =ac_objectget("iMc", i, 8) BtlAddBegEndVCut( sPieceNum, isBeg, nSide, x, y, angleDegR, bevelDegR, angleDegL, bevelDegL ) elseif nType==EMcFrBegHiddenShoe or nType==EMcFrEndHiddenShoe then --! iMc[mindex][2], uihelp_length1: kolouksen leveys --! iMc[mindex][3], uihelp_length2: kolouksen korkeus --! iMc[mindex][4], uihelp_length3: kolouksen syvyys --! iMc[mindex][5], uihelp_length4: uran syvyys (ura on yhtä korkea kuin kolous) --! iMc[mindex][6], uihelp_length5: uran leveys --! iMc[mindex][7], uihelp_length6: alin poraus yKeski --! iMc[mindex][8], uihelp_length7: porausten etäisyys päästä (keskipiste) --! iMc[mindex][9], uihelp_length8: poran halkaisija --! iMc[mindex][10], uihelp_length9: porien välistys pystysuunnassa --! iMc[mindex][11], uihelp_count1: porausten lukumäärä local isBeg, holeWidth, holeHeight, holeDepth, groDepth, groWidth, drillLowest, drillFromEnd, drillSize, drillSpacing, drillCount isBeg=true if nType==EMcFrEndHiddenShoe then isBeg=false end holeWidth =ac_objectget("iMc", i, 2) holeHeight =ac_objectget("iMc", i, 3) holeDepth =ac_objectget("iMc", i, 4) groDepth =ac_objectget("iMc", i, 5) groWidth =ac_objectget("iMc", i, 6) drillLowest =ac_objectget("iMc", i, 7) drillFromEnd=ac_objectget("iMc", i, 8) drillSize =ac_objectget("iMc", i, 9) drillSpacing=ac_objectget("iMc", i, 10) drillCount =ac_objectget("iMc", i, 11) BtlAddHiddenShoe( isBeg, sPieceNum, holeWidth, holeHeight, holeDepth, groDepth, groWidth, drillLowest, drillFromEnd, drillSize, drillSpacing, drillCount ) elseif nType==EMcFrAngledEndOld then info.bEndCut=true nAngleTop=ac_objectget("iMc", i, 2)*PI/180 nAngleSide=ac_objectget("iMc", i, 3)*PI/180 nStraightTop=ac_objectget("iMc", i, 4) nStraightSide=ac_objectget("iMc", i, 5) -- Konversio hirren tyyliin if nAngleSide<0 then nAngleSide=-PI/2-nAngleSide else nAngleSide=PI/2-nAngleSide end -- Päältä puolen vaihto ja konversio hundegger-tyyliin nAngleTop=-nAngleTop if nAngleTop<0 then nAngleTop=-PI/2-nAngleTop else nAngleTop=PI/2-nAngleTop end --toLog( string.format("nAngleTop=%f", nAngleTop) ) BtlAddEndAngledOld( sPieceNum, nAngleSide, nStraightSide, nAngleTop, nStraightTop ) elseif nType==EMcFrOpening then 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 groType=ac_objectget("iMc", i, 11) gtblGros[groFace]=gtblGros[groFace]+1 if gnDestSaw==ESawStromabAdjGro and math.abs(x1-x2)0.001 then dx=dx/z dy=dy/z dx,dy=dy,-dx -- To right x1=x1+dx*textOff y1=y1+dy*textOff x2=x2+dx*textOff y2=y2+dy*textOff end BtlAddMarking(sPieceNum, groFace, x1, y1, x2, y2, xPosText, yPosText, fontSize, 0, s, false, nParallel) end elseif nType==EMcFrReinforce then x1 =ac_objectget("iMc", i, 2) y1 =ac_objectget("iMc", i, 3) dx =ac_objectget("iMc", i, 4) dy =ac_objectget("iMc", i, 5) groFace =ac_objectget("iMc", i, 7) xPosText=2 yPosText=2 if groFace==1 or groFace==3 then fontSize=gnCurrWidth else fontSize=gnProfHeight-gnBtlYoffset end fontSize=fontSize*50 -- 50% and to cm if dx<0.0005 then dx=gnCurrTotLen x1=gnCurrTotLen*0.5 end textOff =0 s=ac_objectget("iMcStr", i, 1) if s=="" then s=string.format("%.0f x %.0f", dx*1000, dy*1000) end s="" -- No text BtlMarkReinforcement( sPieceNum, groFace, x1, y1, dx, dy, xPosText, yPosText, fontSize, textOff, s ) if ac_objectget("iMc", i, 8)~=0 then -- Both sides if groFace==1 then groFace=3 elseif groFace==2 then groFace=4 elseif groFace==3 then groFace=1 elseif groFace==4 then groFace=2 elseif groFace==5 then groFace=6 elseif groFace==6 then groFace=5 end BtlMarkReinforcement( sPieceNum, groFace, x1, y1, dx, dy, xPosText, yPosText, fontSize, textOff, s ) end local xmin=x1-dx*0.5 local xmax=x1+dx*0.5 if info.nFirstStopMark==nil then info.nFirstStopMark=xmin info.y1Stop=y1-dy*0.5 info.y2Stop=info.y1Stop+dy elseif xmininfo.nLastStopMark then info.nLastStopMark=xmax end elseif nType==EMcFrNailGroup then if false then local col, row, cols, rows, anglex, angley x1 =ac_objectget("iMc", i, 2) y1 =ac_objectget("iMc", i, 3) cols =ac_objectget("iMc", i, 4) rows =ac_objectget("iMc", i, 5) anglex =ac_objectget("iMc", i, 6)*PI/180 angley =ac_objectget("iMc", i, 7)*PI/180 dx =ac_objectget("iMc", i, 8) dy =ac_objectget("iMc", i, 9) groFace =ac_objectget("iMc", i, 11) if gnBtlBestPrintSide and groFace~=gnBtlBestPrintSide then if groFace==2 and gnBtlBestPrintSide==4 then groFace=4 elseif groFace==4 and gnBtlBestPrintSide==2 then groFace=2 end end for col=1,cols do for row=1,rows do x=x1 + (col-1)*dx*math.cos(anglex) + (row-1)*dy*math.cos(angley) y=y1 + (col-1)*dx*math.sin(anglex) + (row-1)*dy*math.sin(angley) BtlAddMarking(sPieceNum, groFace, x, y-0.02, x, y+0.02, 2, 2, 0, 0, "", true, 0, false) BtlAddMarking(sPieceNum, groFace, x-0.02, y, x+0.02, y, 2, 2, 0, 0, "", true, 0, false) end end end elseif nType==EMcFrNailLine then -- NOP here elseif nType==EMcFrSaw 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==EMcFrTenonSide then --! ### 400: Tappiliitos tai lohari kylkeen --double dType; // [1] -- --double dMidPos; // [2] Keskipiste kyljessä (x) --double dBotLevel; // [3] Korkeusasema suhteessa alareunaan (y) -- --// Because changed --double dTenonLen; // [4] Tapin pituus --double dTenonWidth; // [5] --double dTenonHeight; // [6] --double dBottomGapOrRotDeg; // [7] Tapin pohjavälys, if dFlags bit0=1 this is rotate angle in degrees. Rotate center is mortise bottom mid (x,y) --double dBorderGapOrDtHousingDepth; // [8] Tapin sivuvälys, if dFlags bit0=1 (from 2014) this is housing depth for mortise (not included in tenon len) -- --double dRoundingRadius; // [9] -- --double dJointSideOneBased; // [10] IFramePlankHandler::SideType+1: 1...6 --double dJointType; // [11] ETypeXXX -- --double dAngleTenonDeg; // [12] Ainakin lohenpyrstössä on alareuna usein kapeampi kuin yläreuna --double dFlags; // [13] Version control, bit0 used local logFace, str, bDove, midPos, borderBot, tenonLen, tenonWidth, tenonHeight, sideGap, bottomGap, angleDove, roundingR, rotAngleDeg, isNew, housingDepth isNew =ac_objectget("iMc", i, 13) % 1 -- bit0: Version control bDove=false if ac_objectget("iMc", i, 11)==2 then bDove=true end midPos =ac_objectget("iMc", i, 2) borderBot =ac_objectget("iMc", i, 3) tenonLen =ac_objectget("iMc", i, 4) tenonWidth =ac_objectget("iMc", i, 5) tenonHeight =ac_objectget("iMc", i, 6) rotAngleDeg=0 bottomGap=0 sideGap=0 housingDepth=0 if isNew then rotAngleDeg =ac_objectget("iMc", i, 7) housingDepth=ac_objectget("iMc", i, 8) else bottomGap =ac_objectget("iMc", i, 7) sideGap =ac_objectget("iMc", i, 8) end roundingR =ac_objectget("iMc", i, 9) logFace =ac_objectget("iMc", i, 10) angleDove =ac_objectget("iMc", i, 12) -- if logFace<5 then -- midPos=midPos+begExtra -- end BtlAddSideJoint( sPieceNum, logFace, bDove, midPos, borderBot, tenonLen+bottomGap, tenonWidth+sideGap*2, tenonHeight, angleDove, roundingR, rotAngleDeg, housingDepth ) elseif nType==EMcFrBalkJoint then --! ### 401: Palkin kaulus --! iMc[mindex][2], uihelp_length1: keskipiste kapulan alusta --! iMc[mindex][3], uihelp_length2: kauluksen leveys --! iMc[mindex][4], uihelp_length3: kolous ylä --! iMc[mindex][5], uihelp_length4: kolous ala --! iMc[mindex][6], uihelp_length5: kolous sivut midPos =ac_objectget("iMc", i, 2) width =ac_objectget("iMc", i, 3) depthTop =ac_objectget("iMc", i, 4) depthBot =ac_objectget("iMc", i, 5) depthSides =ac_objectget("iMc", i, 6) BtlAddBalkMid(sPieceNum, midPos, width, depthTop, depthBot, depthSides) elseif nType==EMcFrBalkShoe then -- NOP on purpose elseif nType~=0 then AddCncErr( ac_objectget("iMc", i, 2), string.format("UNSUPPORTED MACHINING %d", nType) ) end end return info end -- Write ArchiFrame plank BTL-code -- ac_objectopen() must have been called before calling this -- Saves CNC to variable gsCncText -- No return value function BtlWritePlankInt(unidPlank, nSimilarCount, tblSimilarPos, sCol) local s, n, i, x, x1, x2, dx, dy, y, z, sDesignation, sMat, sTimberGrade local nAngleSide, nStraightSide, nAngleTop, nStraightTop, bLine, sStorey local plankinfo if gnBtlLogDir~=EBtlDirBeg then RaiseError( "BTL writing having plank\'s end as reference point unsupported now" ) end gnCncCount=0 gTblCnc=nil gsCncText="" gsCurrentLogId=ac_objectget("#id") plankinfo=af_request("plankinfo") -- For backwards compatibility make sDesignation like below instead of EInfoFullIDPlusUsage -- kappalenro suoraan ID:stä, jos on pelkkää numeroa s=ac_objectget("iUsageId") if string.find(gsCurrentLogId, "[^%d]")==nil and gbBtlForceUnid==false then -- Pelkkää numeroa if gnBtlPieceNum==nil then -- Hätävarana, jos on muitakin kuin pelkkiä numeerisia tunnuksia gnBtlPieceNum=100000 end sPieceNum=string.format( "%06s", gsCurrentLogId ) sDesignation=s else -- ID:ssä muutakin kuin numeroa if gnBtlPieceNum==nil then gnBtlPieceNum=1 end if s~="" then sDesignation=string.format( "%s (%s)", gsCurrentLogId, s) else sDesignation=string.format( "%s", gsCurrentLogId) end sPieceNum=string.format( "%06d", gnBtlPieceNum ) gnBtlPieceNum=gnBtlPieceNum+1 end if gnInfoDesignation~=EInfoFullIDPlusUsage then sDesignation=GetInfoStr(gnInfoDesignation, plankinfo) end if gBtlForceId~=nil then sDesignation=gBtlForceId end -- Headers if gnBtlPlateSide==EBtlMale or gnBtlPlateSide==EBtlFemale then y=gnCurrWidth x=gnProfHeight else x=gnCurrWidth y=gnProfHeight end sStorey="" if gnCurrWidth<0.0181 then sStorey="3" elseif HasReinforce() then sStorey="2" elseif IsIBeam() then sStorey="1" end sMat=GetInfoStr(gnInfoMaterial, plankinfo) -- "Solid-Wood" or "I-Beams" sTimberGrade=GetInfoStr(gnInfoGrade, plankinfo) if sCol==nil then sCol="COLOUR: R:0 G:255 B:0 A:100" end s = string.format( "[PART]\n" .. "SINGLEMEMBERNUMBER: %d\n" .. "ASSEMBLYNUMBER: \"\"\n" .. "ORDERNUMBER: %d\n" .. "DESIGNATION: \"%s\"\n" .. "ANNOTATION: \"\"\n" .. "STOREY: \"%s\"\n" .. "GROUP: \"%s\"\n" .. "STOREYTYPE: WALL\n" .. "ELEMENTNUMBER: \"\"\n" .. "LAYER: 0\n" .. "MODULENUMBER: \"\"\n" .. "PACKAGE: \"\"\n" .. "MATERIAL: \"%s\"\n" .. "TIMBERGRADE: \"%s\"\n" .. "QUALITYGRADE: \"\"\n" .. "COUNT: %d\n" .. "LENGTH: %s\n" .. "HEIGHT: %s\n" .. "WIDTH: %s\n" .. "%s\n" .. "PLANINGLENGTH: 00000000\n" .. "STARTOFFSET: %s\n" .. "ENDOFFSET: %s\n", --"UID: %d\n" , gnBtlPieceNum, gnBtlPieceNum, ToBtl(sDesignation), sStorey, ToBtl(sPieceNum), ToBtl(sMat), ToBtl(sTimberGrade), nSimilarCount, BtlLen2Str(gnCurrTotLen), BtlLen2Str(x), BtlLen2Str(y), sCol, BtlLen2Str(gnBtlStartEndOffset), BtlLen2Str(gnBtlStartEndOffset) ) --gnUid ) gsCncText=s s="" ac_objectclose() for i=1,nSimilarCount do ac_objectopen(tblSimilarPos[i].guid) s=s .. string.format("UID: %d\n%s\n", gnUid, BtlCreatePosStr("TRANSFORMATION", af_request("plankinfo"), gnBtlPlateSide)) ac_objectclose() tblSimilarPos[i].btluid=gnUid gnUid=gnUid+1 end gsCncText=gsCncText .. s ac_objectopen(unidPlank) local info -- # Machinings info=BtlWriteFrameMcs(plankinfo, sPieceNum) -- Reverted 12/2014 back to have these only if markings if IsIBeam() and false then -- We want to have markings if info.y1Stop==nil then info.y1Stop=0 info.y2Stop=gnCurrHeight end if info.nFirstStopMark==nil then info.nFirstStopMark=gnCurrTotLen end if info.nLastStopMark==nil then info.nLastStopMark=0 end end if gbMarkReinforce then if info.nFirstStopMark~=nil then -- Stop mark if any reinforcements and not at begin if info.nFirstStopMark>0.002 then BtlAddMarking(sPieceNum, 2, 0.001, info.y1Stop, 0.001, info.y2Stop, 0, 0, 0, 0, "", true, 0, true) BtlAddMarking(sPieceNum, 4, 0.001, info.y1Stop, 0.001, info.y2Stop, 0, 0, 0, 0, "", true, 0, true) end end if info.nLastStopMark~=nil then -- Stop mark if any reinforcements and not at end if info.nLastStopMark0.5 then xMid=0.5 end BtlAddMarking(sPieceNum, gnBtlBestPrintSide, xMid, 0, xMid, gnCurrHeight, 2, 2, 0, 0, sMark, false, 0) end -- From table to text if gTblCnc~=nil then -- 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) local tblBeg, tblMid, tblEnd, cncindex tblBeg=BtlProfPolyToEdges("iMatProfileXzBeg", gnCurrHeight) tblMid=BtlProfPolyToEdges("iMatProfileXzMid", gnCurrHeight) tblEnd=BtlProfPolyToEdges("iMatProfileXzEnd", gnCurrHeight) if tblEnd then BtlProfPolyToEnd(tblEnd, gnCurrTotLen) end cncindex=1 if tblBeg~=nil then -- Profile at begin, find position for i,v in ipairs(gTblCnc) do if v.pos>0 then cncindex=i break end end cncindex=cncindex+BtlInsertProfTbl(tblBeg, 0, false, gnCurrHeight, gTblCnc, cncindex) end if tblMid~=nil then -- Profile at mid local x1, x2 x1=0 if tblBeg then x1=tblBeg.box.x2 end x2=gnCurrTotLen if tblEnd then x2=tblEnd.box.x1 end while cncindex<=#gTblCnc do v=gTblCnc[cncindex] if v.pos>x1 then break end cncindex=cncindex+1 end cncindex=cncindex+BtlInsertProfTblMid(tblMid, gnCurrHeight, gTblCnc, cncindex, x1, x2) end if tblEnd~=nil then -- Profile at end while cncindex<=#gTblCnc do v=gTblCnc[cncindex] if v.pos>gnCurrTotLen-tblEnd.box.x2 then break end cncindex=cncindex+1 end cncindex=cncindex+BtlInsertProfTbl(tblEnd, 0, true, gnCurrHeight, gTblCnc, cncindex) end for i,v in ipairs(gTblCnc) do gsCncText=gsCncText .. v.text .. string.format("\nPROCESSIDENT: %d\n", i) end end end -- ### Lengthwise profile BEG -- Converts object's table into lines and arcs. Contour goes ccw/right hand side outside -- Returns: nil=no definition, otherwise structure having fields: -- box, structure -- x1,x2,y1,y2 Polygon bound box (x1,y1 always 0) -- hasbot Is bottom other than a line covering x1 -> x2 -- hastop Is top other than a line covering x1 -> x2 -- edges, 1-based table having fields: -- x1,y1 Begin pt -- x2,y2 End point -- isarc true=is arc and following set, nil=line -- midx,midy Arc's center point -- anglebegrad Angle at starting point -- anglelenrad Angle length function BtlProfPolyToEdges(parName, faceHeight) local n, i, v, prevx, prevy, midx, midy, x2, y2, status, dest local tbl, tblBox, tblEdges n=ac_objectget(parName,-1) if n<9 then return nil end x2=0 y2=0 i=1 prevx=nil midx=nil dest=0 tblEdges={} while i+2<=n do x=ac_objectget(parName, i) y=ac_objectget(parName, i+1) status=ac_objectget(parName, i+2) if status<100 then -- Includes end point if x>x2 then x2=x end if y>y2 then y2=y end if prevx and (math.abs(x-prevx)>EPS or math.abs(y-prevy)>EPS) then -- New edge tbl={} tbl.x1=prevx tbl.y1=prevy tbl.x2=x tbl.y2=y dest=dest+1 tblEdges[dest]=tbl end if status<0 then break end prevx=x prevy=y midx=nil elseif status>=900 and status<1000 then -- Circle center midx=x midy=y elseif status>=4000 and status<4100 then -- Arc/full circle, y=angledeg, if angle==360 then x=radius if math.abs(y)>359 then AddCncErr(0, string.format("Unsupported full circle in table %s", parName)) else -- Arc local a, r, dx, dy --ac_environment("tolog", string.format("prevx=%f prevy=%f", prevx, prevy)) tbl={} dy=prevy-midy dx=prevx-midx a=math.atan2(dy, dx) r=math.sqrt(dx*dx + dy*dy) tbl.anglebegrad=a a=a+y*PI/180 tbl.x1=prevx tbl.y1=prevy tbl.x2=midx+math.cos(a)*r tbl.y2=midy+math.sin(a)*r tbl.isarc=true tbl.midx=midx tbl.midy=midy tbl.anglelenrad=y*PI/180 dest=dest+1 tblEdges[dest]=tbl prevx=tbl.x2 prevy=tbl.y2 if tbl.x2>x2 then x2=tbl.x2 end if tbl.y2>y2 then y2=tbl.y2 end end else AddCncErr(0, string.format("Unsupported length shape code %d in table %s", status, parName)) end i=i+3 end tblBox={} tblBox.x1=0 tblBox.y1=0 tblBox.x2=x2 tblBox.y2=y2 tblBox.hasbot=true tblBox.hastop=true -- Check if straight top or bottom for i,v in ipairs(tblEdges) do if not v.isarc and math.abs(v.y1-v.y2)x2-EPS or v.x2x2-EPS) then if v.y1faceHeight-EPS then tblBox.hastop=false end end end tbl={} tbl.box=tblBox tbl.edges=tblEdges --DumpTbl( tbl.edges ) return tbl end -- Swaps coordinates to the plank's end and swaps direction of the polygon (to keep ccw) function BtlProfPolyToEnd(tblEnd, plankLen) local i, v, n for i,v in ipairs(tblEnd.edges) do v.x1,v.y1,v.x2,v.y2=plankLen-v.x2, v.y2, plankLen-v.x1, v.y1 -- Swap coords if v.isarc then v.midx=plankLen-v.midx v.anglebegrad=math.atan2(v.y1-v.midy, v.x1-v.midx) -- Not changing: v.anglelenrad end end -- Need to swap order also n=#tblEnd.edges for i=1,math.floor(n/2) do v=tblEnd.edges[i] tblEnd.edges[i]=tblEnd.edges[n+1-i] tblEnd.edges[n+1-i]=v end tblEnd.box.x1,tblEnd.box.x2=plankLen-tblEnd.box.x2, plankLen-tblEnd.box.x1 end function BtlIsPlankContour(edge, box, faceHeight) if math.abs(edge.y1-edge.y2)faceHeight-EPS then return true end end if math.abs(edge.x1-edge.x2)box.x2 then return true end end -- Not at polygon/plank contour return false end -- tblPoly From BtlProfPolyToEdges -- limitTopBot -1=bottom only, 0=all, 1=top only -- tblCnc cnc-table -- cncindex where to insert the mcs, also fist PROCESSIDENT: x -- x2clip nil=no clipping, value=where to clip the right hand side. Edits polygon if clipping occurs. -- Returns number of machinings added function BtlInsertProfTbl(tblPoly, limitTopBot, findBot, faceHeight, tblCnc, cncindex, x2clip) local i, n, v, i1, cnc, cncindexorg cncindexorg=cncindex -- Find first item for top side (ccw) if begin or bottom if end i1=nil for i,v in ipairs(tblPoly.edges) do if findBot==false then if v.x1+EPS>tblPoly.box.x2 then if i1==nil or v.y1>tblPoly.edges[i1].y1 then i1=i end end else if v.x1-EPS-EPS then begout=true end dist=ac_geo("linedist", x2clip, -1, x2clip, faceHeight+1, v.x2, v.y2) if dist>-EPS then endout=true end if begout and endout then skipEdge=true elseif begout or endout then local xx, yy xx,yy=ac_geo("linex", x2clip, -1, x2clip, faceHeight+1, v.x1, v.y1, v.x2, v.y2) if begout then v.x1=xx v.y1=yy else v.x2=xx v.y2=yy end end end if skipEdge==false then if v.isarc or not BtlIsPlankContour(v, tblPoly.box, faceHeight) then -- Add this edge addCount=addCount+1 tblAdd[addCount]=v v.edgeindex=i end end if limitTopBot<0 then if v.x2+EPS>tblPoly.box.x2 then break end elseif limitTopBot>0 then if v.x1-EPSn then i1=1 end end --DumpTbl(tblAdd) -- Add all collected edges local s, nextindex, isBeg, tbl isBeg=true for i,v in ipairs(tblAdd) do if isBeg then -- Add starting point segtype=10 -- Line contourType=20 -- Mill contour if isBeg then segType=0 -- Starting point contourType=0 -- Fee contour end s=string.format( "PROCESSKEY: 4-250-%d Free contour\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:00000000 P08:00000000 P09:%s P13:00000000", --ADDED WHEN WRITTEN: "PROCESSIDENT: %d", BtlGetSide14(2), -- PROCESSKEY: 4-250-%d BtlLen2Str(v.x1), BtlLen2Str(v.y1), BtlNum2Str(cncindex+1) -- P09 --cncindex -- PROCESSIDENT ) tbl={} tbl.text=s -- pos and count not needed table.insert( tblCnc, cncindex, tbl ) cncindex=cncindex+1 end nextindex=cncindex+1 if i==#tblAdd or v.edgeindex+1~=tblAdd[i+1].edgeindex then -- Terminate path nextindex=0 end s=nil if v.isarc then -- Arc segment local arcx, arcy, a, r -- Calc point at middle of the arc a=v.anglebegrad + v.anglelenrad*0.5 arcx=v.x1-v.midx arcy=v.y1-v.midy r=math.sqrt(arcx*arcx + arcy*arcy) arcx=v.midx+math.cos(a)*r arcy=v.midy+math.sin(a)*r s=string.format( "PROCESSKEY: 4-250-%d Free contour\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:00000000 P08:%s P09:%s P10:%s P11:%s P12:00000000 P13:%s", --ADDED WHEN WRITTEN: "PROCESSIDENT: %d", BtlGetSide14(2), -- PROCESSKEY: 4-250-%d BtlLen2Str(v.x2), BtlLen2Str(v.y2), BtlNum2Str(2), -- P08 BtlNum2Str(nextindex), -- P09 BtlLen2Str(arcx), -- P10 BtlLen2Str(arcy), -- P11 BtlNum2Str(2) -- P13 ) else -- Line segment s=string.format( "PROCESSKEY: 4-250-%d Free contour\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:00000000 P08:%s P09:%s P13:%s", --ADDED WHEN WRITTEN: "PROCESSIDENT: %d", BtlGetSide14(2), -- PROCESSKEY: 4-250-%d BtlLen2Str(v.x2), -- P01 BtlLen2Str(v.y2), -- P02 BtlNum2Str(1), -- P08 BtlNum2Str(nextindex), -- P09 BtlNum2Str(2) -- P13 --cncindex -- PROCESSIDENT ) end if s then tbl={} tbl.text=s -- pos and count not needed table.insert( tblCnc, cncindex, tbl ) cncindex=cncindex+1 end isBeg=false if nextindex==0 then isBeg=true end end return cncindex-cncindexorg end -- Like BtlInsertProfTbl but takes x1,x2 and multiplies polygon on that area & handles top and bottom separately function BtlInsertProfTblMid(tblPoly, faceHeight, tblCnc, cncindex, x1, x2) local xoff, i, v, res, resTot, x2clip resTot=0 xoff=x1 while x1+0.001 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", "") BtlOnInit() sFileName=projNum .. ".btl" sExt="btl" return sFileName, sExt end function SettingsDlg() local tblSettings tblSettings={} tblSettings[1] ={} tblSettings[1].cfgonly =0 tblSettings[1].type =2 tblSettings[1].prompt ="Element built from inside to out (layer numbers)" tblSettings[1].key ="btlinout" tblSettings[1].defvalue =0 tblSettings[2] ={} tblSettings[2].cfgonly =1 tblSettings[2].type =3 tblSettings[2].prompt ="Start and end offset (extra at begin and end of each piece)" tblSettings[2].key ="btlbeoffsets" tblSettings[2].defvalue =0.020 tblSettings[3] ={} tblSettings[3].cfgonly =1 tblSettings[3].type =2 tblSettings[3].prompt ="Save into separate files based on element IDs" tblSettings[3].key ="btlsepfiles" tblSettings[3].defvalue =gnWriteSepFiles tblSettings[4] ={} tblSettings[4].cfgonly =1 tblSettings[4].type =1 tblSettings[4].prompt ="Extend selection" tblSettings[4].key ="btlextsel" tblSettings[4].valuelist="\"1:No - write just selected pieces\",\"2:Write all layers & pieces related to selection\"" tblSettings[4].defvalue =gnExpandSel+1 tblSettings[5] ={} tblSettings[5].cfgonly =1 tblSettings[5].type =1 tblSettings[5].prompt ="DESIGNATION field content" tblSettings[5].key ="des_info" tblSettings[5].defvalue =1 tblSettings[5].valuelist="\"1:Plank full ID (usage), e.g. EW01-01 (ELEM) DEFAULT\",\"2:Role in element (full ID), e.g. Stud (EW-01)\"" tblSettings[6] ={} tblSettings[6].cfgonly =1 tblSettings[6].type =1 tblSettings[6].prompt ="MATERIAL field content" tblSettings[6].key ="mat_info" tblSettings[6].defvalue =1 tblSettings[6].valuelist="\"1:Solid Wood/I-Beams, e.g. Solid Wood DEFAULT\",\"2:Leave empty\"" tblSettings[7] ={} tblSettings[7].cfgonly =1 tblSettings[7].type =1 tblSettings[7].prompt ="TIMBERGRADE field content" tblSettings[7].key ="grade_info" tblSettings[7].defvalue =1 tblSettings[7].valuelist="\"1:Leave empty DEFAULT\",\"2:Grade after last space from mat id (e.g. C24 from 50x100 C24)\"" tblSettings[8] ={} tblSettings[8].cfgonly =0 tblSettings[8].type =2 tblSettings[8].prompt ="Default saw cuts if no ArchiFrameSawCut-objects" tblSettings[8].key ="btlsawcuts" tblSettings[8].defvalue =1 tblSettings[9] ={} tblSettings[9].cfgonly =1 tblSettings[9].type =1 tblSettings[9].prompt ="ArchiFrameSawCut generated codes" tblSettings[9].key ="btlsawcutcodes" tblSettings[9].valuelist="\"1:P13=2, P14=0 (default)\",\"2:Saw cut, P13=1, P14=1223; Milling P13=2, P14=1228\"" tblSettings[9].defvalue =gnExpandSel+1 tblSettings[10] ={} tblSettings[10].cfgonly =0 tblSettings[10].type =2 tblSettings[10].prompt ="Mark reinforcements" tblSettings[10].key ="btlmarkreinforce" tblSettings[10].defvalue =1 tblSettings[11] ={} tblSettings[11].cfgonly =0 tblSettings[11].type =1 tblSettings[11].prompt ="Board polygon processing behaviour" tblSettings[11].key ="grade_info" tblSettings[11].defvalue =1 tblSettings[11].valuelist="\"1:PROCESS: NO (default)\",\"2:PROCESS: YES\",\"3:PROCESS: NO + Generate machinings\"" tblSettings[12] ={} tblSettings[12].cfgonly =1 tblSettings[12].type =1 tblSettings[12].prompt ="Scaleunit/Precision" tblSettings[12].key ="scaleunit" tblSettings[12].defvalue =1 tblSettings[12].valuelist="\"1:1/10 mm (default)\",\"2:1/100 mm\"" tblSettings[13] ={} tblSettings[13].cfgonly =0 tblSettings[13].type =1 tblSettings[13].prompt ="Destination saw type" tblSettings[13].key ="destsaw" tblSettings[13].defvalue =1 tblSettings[13].valuelist="\"1:Default\",\"2:Stromab saw\",\"3:Stromab saw adjust grooves at beg/end 37->38, 49->50\"" tblSettings[14] ={} tblSettings[14].cfgonly =1 tblSettings[14].type =1 tblSettings[14].prompt ="Plank rotation in BTL (ref side 1)" tblSettings[14].key ="btlreferenceside" tblSettings[14].defvalue =1 tblSettings[14].valuelist="\"1:Top (default)\",\"2:Front\",\"3:Bottom\", \"4:Back\"" gHelpAnchor="afdlgbtlexport" bRes,sErr=ac_optiondlg("LDBW", "BTL-writing settings", tblSettings) if not bRes then return false end gbElemLayerInToOut=false if tblSettings[1].value~=0 then gbElemLayerInToOut=true end gnBtlStartEndOffset=tblSettings[2].value gnWriteSepFiles=tblSettings[3].value gnExpandSel=tblSettings[4].value-1 -- Infos gnInfoDesignation=EInfoFullIDPlusUsage if tblSettings[5].value==2 then gnInfoDesignation=EInfoPlankRoleIdFull end gnInfoMaterial=EInfoSolidOrIBeam if tblSettings[6].value==2 then gnInfoMaterial=EInfoNone end gnInfoGrade=EInfoNone if tblSettings[7].value==2 then gnInfoGrade=EInfoGrade end gbBtlDefSawCuts=false if tblSettings[8].value==1 then gbBtlDefSawCuts=true end -- Old defaults gnSawCutObjSawP13=2 gnSawCutObjSawP14=0 gnSawCutObjMillP13=2 gnSawCutObjMillP14=0 if tblSettings[9].value==2 then -- From NEFA for H&M gnSawCutObjSawP13=1 gnSawCutObjSawP14=1223 gnSawCutObjMillP13=2 gnSawCutObjMillP14=1228 end gbMarkReinforce=true if tblSettings[10].value==0 then gbMarkReinforce=false end gbMachiningsToBoard=false gbArchiFrameBoardsPolyProcess=false if tblSettings[11].value==3 then gbMachiningsToBoard=true elseif tblSettings[11].value==2 then gbArchiFrameBoardsPolyProcess=true end gnBtlScaleUnit=tblSettings[12].value gnDestSaw=tblSettings[13].value gnBtlPlateSide=EBtlMale local refSide=tblSettings[14].value if refSide==1 then gnBtlPlateSide=EBtlMale elseif refSide==2 then gnBtlPlateSide=EBtlFrontSide elseif refSide==3 then gnBtlPlateSide=EBtlFemale elseif refSide==4 then gnBtlPlateSide=EBtlBackSide end return true end -- Called to do all that needs to be done -- strFileName is full path name for the result file function OnSaveList(strFileName) -- Edit settings here gnBtlPlateSide=EBtlMale gnBtlLogDir=EBtlDirBeg gnBtlPieceNum=nil gBtlForceId=nil gnUid=1 gnOrigX,gnOrigY,gnOrigZ=ac_environment("userorigin") gnWarnings=0 gbElemLayerInToOut=false if not SettingsDlg() then gbCancel=true return end OnSaveListIntElem(strFileName, true) if false then -- Debug all feeding variations local dir, plate ac_msgbox( "TESTING, PLANK FED ALL WAYS" ) gbBtlForceUnid=true for dir=0,0 do gnBtlLogDir=dir for plate=0,3 do gnBtlPlateSide=plate gBtlForceId=string.format("%s gnBtlPlateSide=%d", gsCurrentLogId, plate) gtblUsedIds = {} OnSaveListInt(strFileName, false) gBtlForceId=nil end end end end -- Stromab F600 saw requires version number to 10.4 function GetBtlVersionNumber() if gnDestSaw==ESawStromab or gnDestSaw==ESawStromabAdjGro then return "10.4" end return "10.6" end -- Stromab F600 saw requires the P09 parameter for the groove (also called lap joint or milling) to be 0 function GetBtlGrooveP09() if gnDestSaw==ESawStromab or gnDestSaw==ESawStromabAdjGro then return 0 end return 90 end -- ACTUALLY NEVER USED, OnSaveListIntElem used instead -- Called to do all that needs to be done -- strFileName is full path name for the result file function OnSaveListInt(strFileName, bWriteHeader) local sProjId=ac_environment("parsetext", "") local nSimilarCount, tblSimilarPos -- Start writing sTotPlank="" i=1 while true do v=gTblPlanks[i] if v==nil then break end ac_objectopen(v) gsCurrentLogId=ac_objectget("#id") CheckId(gsCurrentLogId) -- Lasketaan montako on samalla id:llä ja täräytetään ne kerralla nSimilarCount=1 tblSimilarPos={} tblSimilarPos[nSimilarCount]={} tblSimilarPos[nSimilarCount].guid=v ac_objectclose() while gTblPlanks[i+nSimilarCount]~=nil do ac_objectopen(gTblPlanks[i+nSimilarCount]) if gsCurrentLogId~=ac_objectget("#id") then ac_objectclose() break end nSimilarCount=nSimilarCount+1 tblSimilarPos[nSimilarCount]={} tblSimilarPos[nSimilarCount].guid=gTblPlanks[i+nSimilarCount-1] ac_objectclose() end ac_objectopen(v) WriteBtlPlank( v, nSimilarCount, tblSimilarPos ) ac_objectclose(v) sTotPlank=sTotPlank .. gsCncText i=i+nSimilarCount end -- Frame-kapulat -> btl if bWriteHeader then hFile=io.open(strFileName, "wt") else hFile=io.open(strFileName, "at") end io.output( hFile ) if bWriteHeader then -- btl-header... local hFile=io.open(strFileName, "at") local s local projNum = ac_environment("parsetext", "") local projName = ac_environment("parsetext", "") local custName = ac_environment("parsetext", "") s=string.format( "VERSION: \"BTL V%s\"\n" .. "BUILD: \"10400\"\n" .. "EDITION: PREFABRICATION\n" .. "[GENERAL]\n" .. "PROJECTNUMBER: \"%s\"\n" .. "PROJECTNAME: \"%s\"\n" .. "PROJECTPART: \"\"\n" .. "LISTNAME: \"ARCHIFRAME\"\n" .. "CUSTOMER: \"%s\"\n" .. "ARCHITECT: \"\"\n" .. "EDITOR: \"\"\n" .. --"DELIVERYDATE: \"0:00:00\"\n" .. --"EXPORTDATE: \"2011-12-12\"\n" .. --"EXPORTTIME: \"14:54:11\"\n" .. "EXPORTRELEASE: \"ArchiFrame BTL 2012-06-01\"\n" .. "LANGUAGE: \"EN\"\n" .. "SCALEUNIT: %d\n" .. "PROCESSINGQUALITY: AUTOMATIC\n" .. "COMPUTERNAME: \"\"\n" .. "USER: \"\"\n" .. "SOURCEFILE: \"\"\n" .. "EXPORTFILE: \"\"\n" .. "RECESS: AUTOMATIC\n" .. "COMMENT: \"\"\n", GetBtlVersionNumber(), ToBtl(projNum), ToBtl(projName), ToBtl(custName), gnBtlScaleUnit ) io.write(s) gnBtlPieceNum=1 -- Global piece number -- ...btl-header end io.write( sTotPlank ) io.close( hFile ) gsCncText="" end ----------------------------------------------------------------------------- -- ELEMENT BTL -- Called to parse tblPlank.elemgroup and tblPlank.elemgroupsort function SetElemGroup(tblPlank) local group, sort, usage group=ac_objectget("iElemGroup") sort=nil if group==nil then group="" -- board end if string.match(group, "^vertical_%a*") then group="Stud" elseif string.match(group, "^bottom_%a*") or string.match(group, "^2ndbottom_%a*") then group="Bottom" elseif string.match(group, "^top_%a*") or string.match(group, "^2ndtop_%a*") or string.match(group, "^contourtilted%a*") then group="Top" elseif string.match(group, "^balktop%a*") then group="Top beam" elseif string.match(group, "^lintel%a*") then group="Lintel" end if tblPlank.tblOwnerElem.type=="intstud" then group="Internal studding" sort="1" .. group end if tblPlank.tblOwnerElem.type=="extstud" then group="External studding" sort="2" .. group end -- Override if iUsage not in default value ELEM usage=ac_objectget("iUsageId") if usage==nil then usage="" -- board end if usage~="ELEM" and usage~="" then group=usage end if sort==nil then sort="0" .. group end tblPlank.elemgroup=group tblPlank.elemgroupsort=sort -- To be able to sort differently from group name end function CmpElem(elem1, elem2) if elem1.idelem2.id then return 1 end if elem1.guid==elem2.guid then return 0 end if elem1.guidn2 function CmpElemPlank(n1, n2) -- Element id local res res=CmpElem(n1.tblMaster, n2.tblMaster) if res~=0 then return res end -- Same master, check layer index of the element if n1.tblOwnerElem.indexn2.tblOwnerElem.index then return 1 end -- Owners having same zoff res=CmpElem(n1.tblOwnerElem, n2.tblOwnerElem) if res~=0 then return res end if n1.plankinfo.type~=n2.plankinfo.type then return n1.plankinfo.type-n2.plankinfo.type end -- Plank grouping text, NOT at cnc - just the plank id -- if n1.elemgroupsortn2.elemgroupsort then -- return 1 -- end -- Plank id (actually table was sorted originally just by plank id) if n1.idn2.id then return 1 end -- To produce similar files in different saves if n1.guidn2.guid then return 1 end return 0 end -- Returns two tables: tblElemPlanks (belonging to an element), tblOtherPlanks (not belonging to any element) -- tblElemPlanks items have following information -- key=index, value=tbl: -- (Also for tblOtherPlanks) -- guid=plank guid -- id=plank id -- index=for sorting -- btluid=set when plank has been written, the btl UID: -- (Not for tblOtherPlanks) -- elemgroup=usage in element (iElemGroup from plank object adjusted to something) -- elemgroupsort=sort key for the group -- tblOwnerElem=direct owner having fields as tblMaster -- tblMaster=link to tblElemGuid2Master value for the master element of this multilayer element, same guid as tblOwnerElem if single layer element -- guid -- type -- index=layer index if multilayer element -- plankinfo=the geometry information -- Following only for tblMaster -- floor=home floor -- id=element info/ID function MakeElemTbl(tblSources) local i, v, tblElemGuid2Master, tblElemGuid2Owner, tblPlanksData, sId, plankInfo, elemMaster, elemOwner, tblPlank, nPlanks, cnc local tblPlanksOther, nPlanksOther -- Do the grouping by master element here, for performance have a helper table: element guid -> table master element info (guid, id) tblElemGuid2Master={} -- key=element guid, value=tbl with fields: guid=master element guid, id=its id for sorting, type=type attribute from xml tblElemGuid2Owner={} -- As previous, but only guid, type tblPlanksData={} tblPlanksOther={} nPlanks=0 nPlanksOther=0 for i,v in ipairs(tblSources) do tblPlank=v -- Nope, take base table and edit it {} -- May be board as well ac_objectopen(v.guid) cnc=ac_objectget("iCnc") if not cnc or cnc~=0 then plankInfo=af_request("plankinfo") tblPlank.guid=v.guid tblPlank.id=ac_objectget("#id") tblPlank.plankinfo=plankInfo -- Skip if not part of an element if plankInfo.ownerelemguid==nil then if v.type==1 or v.type==4 then nPlanksOther=nPlanksOther+1 tblPlank.index=nPlanksOther tblPlanksOther[nPlanksOther]=tblPlank end else elemMaster=tblElemGuid2Master[plankInfo.ownerelemguid] elemOwner=tblElemGuid2Owner[plankInfo.ownerelemguid] if elemMaster==nil or elemOwner==nil then -- Open the parent element local elemParent, iElem, vElem, elemMasterTemp elemParent=af_request("elem_openparent", plankInfo.ownerelemguid) elemMaster=tblElemGuid2Master[elemParent.guid] elemMasterTemp={} elemMasterTemp.guid=elemParent.guid elemMasterTemp.plankinfo=af_request("plankinfo", elemParent.guid) elemOwner={} elemOwner.guid=plankInfo.ownerelemguid elemOwner.plankinfo=af_request("plankinfo", plankInfo.ownerelemguid) for iElem, vElem in ipairs(elemParent.tblelems) do if vElem.guid==elemParent.guid then elemMasterTemp.type=vElem.type elemMasterTemp.index=iElem elemMasterTemp.type=vElem.type elemMasterTemp.thickness=vElem.thickness elemMasterTemp.elemtypeid=vElem.id end if vElem.guid==plankInfo.ownerelemguid then elemOwner.type=vElem.type elemOwner.index=iElem elemOwner.type=vElem.type elemOwner.thickness=vElem.thickness elemOwner.elemtypeid=vElem.id end end if elemMasterTemp.type==nil or elemOwner==nil then RaiseError( string.format("Cannot find parent element for plank %s", v) ) end ac_objectclose() ac_objectopen(elemMasterTemp.guid) elemMasterTemp.id=ac_objectget("#id") elemMasterTemp.floor=ac_objectget("#floor") ac_objectclose() ac_objectopen(elemOwner.guid) elemOwner.id=ac_objectget("#id") elemOwner.floor=ac_objectget("#floor") ac_objectclose() ac_objectopen(v.guid) if elemMaster==nil then elemMaster=elemMasterTemp end tblElemGuid2Master[elemParent.guid]=elemMaster tblElemGuid2Master[plankInfo.ownerelemguid]=elemMaster tblElemGuid2Owner[plankInfo.ownerelemguid]=elemOwner end nPlanks=nPlanks+1 tblPlank.index=nPlanks tblPlank.tblMaster=elemMaster tblPlank.tblOwnerElem=elemOwner SetElemGroup(tblPlank) tblPlanksData[nPlanks]=tblPlank end end ac_objectclose() end -- Sort by master element guid and plank index table.sort(tblPlanksData, function (n1, n2) local cmp cmp=CmpElemPlank(n1, n2) if cmp~=0 then return cmp<0 end return n1.index < n2.index -- Keep the order if at same pos (id is part of sort order) end) return tblPlanksData, tblPlanksOther end -- tblPlanks has at leat fields: id, guid -- Sets field btluid for the plank -- i is index to tblPlanks -- iMax is the last index that may be used -- Returns: -- index to next unprocessed item or nil=at the end of table -- cnc-text (even if index==nil), tblSimilarPos for all the planks indexed [1]...[n]; each item has tblSimilarPos[i].btluid set -- plateSide (EBtlFemale if plank dir swapped) function WriteSimilarPlanks(tblPlanks, i, iMax) local sTotPlank, v, sGuid, nSimilarCount, i1, tblSimilarPos, plateSide, sCol, index -- Start writing sTotPlank="" v=tblPlanks[i] if v==nil or i>iMax then return nil, sTotPlank end ac_objectopen(v.guid) gsCurrentLogId=ac_objectget("#id") CheckId(gsCurrentLogId) -- Lasketaan montako on samalla id:llä ja täräytetään ne kerralla nSimilarCount=1 tblSimilarPos={} tblSimilarPos[nSimilarCount]=v ac_objectclose() while tblPlanks[i+nSimilarCount]~=nil and i+nSimilarCount<=iMax do ac_objectopen(tblPlanks[i+nSimilarCount].guid) if gsCurrentLogId~=ac_objectget("#id") then ac_objectclose() break end nSimilarCount=nSimilarCount+1 tblSimilarPos[nSimilarCount]=tblPlanks[i+nSimilarCount-1] ac_objectclose() end -- Decide colour based on index sCol="COLOUR: R:0 G:255 B:0 A:100" if v.tblOwnerElem~=nil then index=v.tblOwnerElem.index if index==2 then sCol="COLOUR: R:255 G:0 B:0 A:100" elseif index==3 then sCol="COLOUR: R:0 G:0 B:255 A:100" elseif index==4 then sCol="COLOUR: R:128 G:128 B:0 A:100" elseif index==5 then sCol="COLOUR: R:128 G:0 B:128 A:100" elseif index==6 then sCol="COLOUR: R:0 G:128 B:128 A:100" elseif index==7 then sCol="COLOUR: R:256 G:256 B:0 A:100" elseif index==8 then sCol="COLOUR: R:256 G:0 B:256 A:100" elseif index==9 then sCol="COLOUR: R:0 G:256 B:256 A:100" end end ac_objectopen(v.guid) plateSide=WriteBtlPlank( v.guid, nSimilarCount, tblSimilarPos, sCol ) ac_objectclose(v.guid) sTotPlank=sTotPlank .. gsCncText --ac_environment( "tolog", string.format("nSimilarCount=%d", nSimilarCount) ) for i1=1,nSimilarCount do tblPlanks[i+i1-1].btluid=tblSimilarPos[i1].btluid tblPlanks[i+i1-1].plateside=plateSide end i=i+nSimilarCount return i, sTotPlank, plateSide end -- 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 -- tblPoly For example af_request("getpoly", nil, elem.guid) -- tblPolyPoint For example af_request("getpoly", nil, elem.guid).poly -- sProcess YES or NO -- nSideLayer Given if layer to be processed (1), also no swap for x/y if given -- Returns cnc text function BtlGetPoly(tblPoly, tblPolyPoints, sProcess, nSideLayer, bRotate) local nSide, s, sOrg, i, v, segType, sCnc, sType, begInd, x, y nSide=2 --- Fixed, plank feeding dir may be changed: BtlGetSide14(2) -- Front surface, x needs to be mirrored if nSideLayer then nSide=nSideLayer end s=string.format("PROCESS: %s ", sProcess) sOrg=s segType="00000000" sType="OUTLINE" sCnc="" begInd=1 for i,v in ipairs(tblPolyPoints) do x=v.x y=v.y if not nSideLayer then x,y=y,tblPoly.x2-x end -- If plank machinings (grooves, drillings etc...) are added to a board the outline has to be rotated counter clockwise 90 degrees -- to match the rotation of the machinings if bRotate then x,y=-y,x x=x+tblPoly.x2-tblPoly.x1 end sCnc=sCnc .. string.format( "%s: SIDE: %d %sP01:%s P02:%s P03:00000000 P08:%s\n", sType, nSide, s, BtlLen2Str(x), BtlLen2Str(y), segType) s="" segType=BtlNum2Str(1) -- "00000010" if v.isendcontour then -- Duplicate start point x=tblPolyPoints[begInd].x y=tblPolyPoints[begInd].y if not nSideLayer then x,y=y,tblPoly.x2-x end -- If plank machinings (grooves, drillings etc...) are added to a board the outline has to be rotated counter clockwise 90 degrees -- to match the rotation of the machinings if bRotate then x,y=-y,x x=x+tblPoly.x2-tblPoly.x1 end sCnc=sCnc .. string.format( "%s: SIDE: %d P01:%s P02:%s P03:00000000 P08:%s\n", sType, nSide, BtlLen2Str(x), BtlLen2Str(y), segType) if v.isendpolygon then break end sType="APERTURE" segType="00000000" begInd=i+1 end end return sCnc end function AlmostEq(a, b) return math.abs(a-b)0.0 then AddCncErr(pt1.x, string.format("ERROR: Invalid shape starting at (x=%0.2f, y=%0.2f). Only rectangular shapes are supported.", pt1.x*1000.0,pt1.y*1000.0)) invalidShape=true break end -- At least one corner, increment i if BtlGetNextContourIdx(tblPoly, i)~=1 then i=i+1 end -- Do we have 2 perpendicular corners in a clock-wise winding order if AlmostEq(Dot2(v2,v3), 0.0) and CalcCross2D(v2,v3)<0.0 then -- Yes, increment i if BtlGetNextContourIdx(tblPoly, i)~=1 then i=i+1 end -- Need to extend pt1 for the groove to be rectangular: -- -- p1____________p2 -- groove | -- _______________| -- p4 p3 -- if Distance2D(pt3, pt4)>Distance2D(pt1, pt2) then pt1=Sub2D(pt4, v2) end end -- Do we have 3 perpendicular corners in a clock-wise winding order if AlmostEq(Dot2(v3,v4), 0.0) and CalcCross2D(v3,v4)<0.0 then -- Yes, increment i if BtlGetNextContourIdx(tblPoly, i)~=1 then i=i+1 end -- / -- /p1________p2 -- | -- groove | -- /|p4_________ p3 -- /_______________ -- -- Need to use pt2 and pt3 as source points and extend "to the left" in the above picture (using distance of p4 -> p3) pt1=pt2 pt2=pt3 pt3=pt4 end BtlAddRectangularGro( sPieceNum, pt1, pt2, Distance2D(pt3, pt2) ) end if BtlGetNextContourIdx(tblPoly, i)~=1 then i=i+1 else break end end if invalidShape then return end -- Add grooves from polygon holes, -- Only holes with 4 points that form a rectangular shape is allowed for j=i+1, #tblPoly.poly, 4 do local pt1=tblPoly.poly[j+0] local pt2=tblPoly.poly[j+1] local pt3=tblPoly.poly[j+2] local pt4=tblPoly.poly[j+3] local v1=Sub2D(pt2, pt1) local v2=Sub2D(pt3, pt2) local v3=Sub2D(pt4, pt3) local v4=Sub2D(pt1, pt4) if not (pt1.isbeghole and pt4.isendcontour) or -- Start and ends with a hole pt2.isendpolygon or pt2.isbeghole or pt2.isendcontour or pt3.isendpolygon or pt3.isbeghole or pt3.isendcontour or -- Holes with less than 4 points pt4.isbeghole or not (AlmostEq(Dot2(v1,v2), 0.0) and AlmostEq(Dot2(v3,v4), 0.0)) then -- 4 points does not form a rectangular shape AddCncErr(pt1.x, string.format("ERROR: Invalid window shape starting at (x=%0.2f, y=%0.2f). Only rectangular windows are supported.", pt1.x*1000.0,pt1.y*1000.0)) break end BtlAddRectangularGro( sPieceNum, pt1, pt2, Distance2D(pt3, pt2) ) end end -- Adds groove machinings from board edge structure function BtlAddMachiningsFromEdgeToBoard() for i=1, ac_objectget("iMcEdge" ,-1)-1 do local nType = NormalizeType(ac_objectget("iMcEdge", i, 1)) if nType==EMcEdgeGroove then local edgeId =ac_objectget("iMcEdge", i, 2) local dSide24 =ac_objectget("iMcEdge", i, 3) local dDepth =ac_objectget("iMcEdge", i, 4) local dWidth =ac_objectget("iMcEdge", i, 5) local dOffFromSide =ac_objectget("iMcEdge", i, 6) local dAngleCrossDeg =ac_objectget("iMcEdge", i, 7) local dExtBeg =ac_objectget("iMcEdge", i, 8) local dExtExtend =ac_objectget("iMcEdge", i, 9) local countourBeg=1 for j=1, ac_objectget("iPolygon", -1) do local polyEdgeId =ac_objectget("iPolygon", j, 9) local mask =ac_objectget("iPolygon", j, 3) if polyEdgeId==edgeId then local x1=ac_objectget("iPolygon", j, 1) local y1=ac_objectget("iPolygon", j, 2) local x2=ac_objectget("iPolygon", countourBeg, 1) local y2=ac_objectget("iPolygon", countourBeg, 2) if mask~=-1 then x2=ac_objectget("iPolygon", j+1, 1) y2=ac_objectget("iPolygon", j+1, 2) end local dX=math.abs(x2-x1) local dY=math.abs(y2-y1) local length=math.sqrt(dX*dX+dY*dY) if length>EPS then local dirX=dX/length local dirY=dY/length if y1>y2 then y1=y1+dirY*dExtBeg y2=y2-dirY*dExtExtend else y1=y1-dirY*dExtBeg y2=y2+dirY*dExtExtend end if x1>x2 then x1=x1+dirX*dExtBeg x2=x2-dirX*dExtExtend else x1=x1-dirX*dExtBeg x2=x2+dirX*dExtExtend end BtlAddGro(sPieceNum, x1, y1, x2, y2, dWidth, dDepth, 0.0, dSide24, 0.0) end end if mask==-1 then countourBeg=j+1 end end else AddCncErr( 0, string.format("UNSUPPORTED EDGE MACHINING %d", nType) ) end end end -- Uses tblBoard.tblSimilar to write position information for each function BtlWriteBoard(tblBoard) local sCnc, i, v, nSide, tblPoly, s ac_objectopen(tblBoard.guid) gsCurrentLogId="BOARD " .. tblBoard.id gsCurrentLogGuid=tblBoard.guid tblPoly=tblBoard.poly local length=tblPoly.y2-tblPoly.y1 local height=tblPoly.x2-tblPoly.x1 -- If plank machinings (grooves, drillings etc...) are added to a board the dimensions of the board must be flipped. -- By default tblPoly.x2-tblPoly.x1 is used as the height parameter for boards by default. if gbMachiningsToBoard then length, height=height, length end sCnc = string.format( "[PART]\n" .. "SINGLEMEMBERNUMBER: %d\n" .. "ASSEMBLYNUMBER: \"\"\n" .. "ORDERNUMBER: %d\n" .. "DESIGNATION: \"%s\"\n" .. "LAYER: -1\n" .. "MATERIAL: \"%s\"\n" .. "TIMBERGRADE: \"\"\n" .. "QUALITYGRADE: \"\"\n" .. "COUNT: %d\n" .. "LENGTH: %s\n" .. "HEIGHT: %s\n" .. "WIDTH: %s\n" .. "COLOUR: R:64 G:64 B:64 A:100\n" .. "PLANINGLENGTH: 00000000\n" .. "STARTOFFSET: 00000000\n" .. "ENDOFFSET: 00000000\n", gnBtlBoardPieceNum, gnBtlBoardPieceNum, tblBoard.id, ac_objectget("iMatId"), #tblBoard.tblSimilar, BtlLen2Str(length), BtlLen2Str(height), BtlLen2Str(ac_objectget("iThickness")) ) ac_objectclose() for i=1,#tblBoard.tblSimilar do ac_objectopen(tblBoard.tblSimilar[i].guid) -- If plank machinings (grooves, drillings etc...) are written to the board we have to use the same transformation as when writing planks. if gbMachiningsToBoard then sCnc=sCnc .. string.format("UID: %d\n%s\n", gnUid, BtlCreatePosStr("TRANSFORMATION", af_request("plankinfo"), gnBtlPlateSide)) else sCnc=sCnc .. string.format("UID: %d\n%s\n", gnUid, BtlCreatePosStrBoard("TRANSFORMATION", af_request("plankinfo"), gPlankTranMat)) end ac_objectclose() tblBoard.tblSimilar[i].btluid=gnUid gnUid=gnUid+1 end -- Write outline local yesno yesno="NO" if gbArchiFrameBoardsPolyProcess then yesno="YES" end sCnc=sCnc .. BtlGetPoly(tblPoly, tblPoly.poly, yesno, nil, gbMachiningsToBoard) if gbMachiningsToBoard then ac_objectopen(tblBoard.tblSimilar[1].guid) BtlAddMachiningsFromMachiningListToBoard() BtlAddMachiningsFromPolygonToBoard(tblPoly) BtlAddMachiningsFromEdgeToBoard() ac_objectclose() if gTblCnc~=nil then -- 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) for i,v in ipairs(gTblCnc) do sCnc=sCnc .. v.text .. string.format("\nPROCESSIDENT: %d\n", i) end end end return sCnc end -- Writes all the boards -- Returns the cnc text function BtlSortAndWriteBoards(tblBoards) local i, sCnc, i2, v -- Sort by id table.sort(tblBoards, function (n1, n2) if n1.id~=n2.id then return n1.id0.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 s=string.format( "PROCESSKEY: %d-250-%d POLYGON PT\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:%s P08:00000000 P09:%s P13:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nToolSide, nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(prev.x), -- P01 BtlLen2Str(prev.y), -- P02 BtlInt2Str(1), -- P07 BtlInt2Str(nProcessIdent+1), -- P09 BtlInt2Str(2), -- P13 BtlInt2Str(p15), nProcessIdent ) nProcessIdent=nProcessIdent+1 sCnc=sCnc .. s s=string.format( "PROCESSKEY: %d-250-%d POLYGON PT\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:%s P08:%s P09:00000000 P13:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nToolSide, nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(v.x), -- P01 BtlLen2Str(v.y), -- P02 BtlInt2Str(1), -- P07 BtlInt2Str(1), -- P08 BtlInt2Str(2), -- P13 BtlInt2Str(p15), nProcessIdent ) nProcessIdent=nProcessIdent+1 sCnc=sCnc .. s end prev=v if v.isendcontour then -- Duplicate start point endv=tblPoly.poly[begInd] if prev.overbegin<=0.001 and endv.overendin<=0.001 then p15=4 -- Over at both ends elseif prev.overbegin<=0.001 and endv.overendin>0.001 then p15=2 -- Over at begin elseif prev.overbegin>0.001 and endv.overendin<=0.001 then p15=3 -- Over at end else p15=1 -- No over end s=string.format( "PROCESSKEY: %d-250-%d POLYGON PT\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:%s P08:00000000 P09:%s P13:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nToolSide, nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(prev.x), -- P01 BtlLen2Str(prev.y), -- P02 BtlInt2Str(1), -- P07 BtlInt2Str(nProcessIdent+1), -- P09 BtlInt2Str(2), -- P13 BtlInt2Str(p15), nProcessIdent ) nProcessIdent=nProcessIdent+1 sCnc=sCnc .. s s=string.format( "PROCESSKEY: %d-250-%d POLYGON PT\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS: P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:%s P08:%s P09:00000000 P13:%s P15:%s\n" .. "PROCESSIDENT: %d\n", nToolSide, nSide, BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(endv.x), -- P01 BtlLen2Str(endv.y), -- P02 BtlInt2Str(1), -- P07 BtlInt2Str(1), -- P08 BtlInt2Str(2), -- P13 BtlInt2Str(p15), nProcessIdent ) nProcessIdent=nProcessIdent+1 sCnc=sCnc .. s if v.isendpolygon then break end begInd=i+1 prev=nil -- Nope, openings are also reversed and tool on the left side: nToolSide=4 -- On the right hand side end end return sCnc,nProcessIdent end -- DoExpandSel() calls, returns adjusted dest function DoExpandParts(tblParts, tblRes, dest, tblHandledPlanks) local i,v,tbl,info if tblParts==nil then return dest end for i,v in ipairs(tblParts) do if tblHandledPlanks[v.guid]==nil then tblHandledPlanks[v.guid]=true ac_objectopen(v.guid) info=af_request("plankinfo") tbl={} tbl.guid=v.guid tbl.ptr=info.ptr tbl.id=ac_objectget("#id") tbl.type=info.type ac_objectclose() dest=dest+1 tblRes[dest]=tbl end end return dest end -- Extends "getsellisting" to contain all pieces related to an element function DoExpandSel(tblSources) local tblHandledPlanks -- Also boards local tblRes local source, v, dest, info tblHandledPlanks={} tblRes={} dest=0 for source,v in ipairs(tblSources) do if tblHandledPlanks[v.guid]==nil then -- Not added yet tblHandledPlanks[v.guid]=true dest=dest+1 tblRes[dest]=v info=af_request("plankinfo", v.guid) if info.ownerelemguid then local q, ielem, velem, ipart, vpart q=af_request ("elem_quantities", info.ownerelemguid, 1) for ielem,velem in ipairs(q.tblelems) do dest=DoExpandParts(velem.tblplanks, tblRes, dest, tblHandledPlanks) dest=DoExpandParts(velem.tblboards, tblRes, dest, tblHandledPlanks) end end end end return tblRes 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 function DoFileName(strFileName, strAdd) local path, fname, ext path, fname, ext=SplitFileName(strFileName, strNum) return string.format("%s%s%s%s", path, fname, strAdd, ext) end -- Saves btl as element -- strFileName is full path name for the result file function OnSaveListIntElem(strFileName, bWriteHeader) local tblPlanksElem, tblPlanksOther local tblSources tblSources=af_request("getsellisting") if gnExpandSel==1 then tblSources=DoExpandSel(tblSources) end -- Do the grouping by master element here, for performance have a helper table: element guid -> table master element info (guid, id) tblPlanksElem, tblPlanksOther=MakeElemTbl(tblSources) if gnWriteSepFiles==0 then -- All to single file DoWriteSingleFile(strFileName, bWriteHeader, tblPlanksElem, tblPlanksOther) else -- Separate files if tblPlanksElem and tblPlanksElem[1] then local tblCurrElem, dest, source, fname source=1 while tblPlanksElem[source]~=nil do -- Find until master element changes tblCurrElem={} tblCurrElem[1]=tblPlanksElem[source] dest=1 while tblPlanksElem[source+dest] and tblPlanksElem[source].tblMaster==tblPlanksElem[source+dest].tblMaster do tblCurrElem[dest+1]=tblPlanksElem[source+dest] dest=dest+1 end source=source+dest gnUid=1 fname=DoFileName(strFileName, "_" .. tblCurrElem[1].tblMaster.id) DoWriteSingleFile(fname, bWriteHeader, tblCurrElem, nil) end end if tblPlanksOther and tblPlanksOther[1] then gnUid=1 DoWriteSingleFile(strFileName, bWriteHeader, nil, tblPlanksOther) end end end -- Writes single btl-file -- tblPlanksElem Related to elements, may be nil -- tblPlanksOther Independent planks, may be nil function DoWriteSingleFile(strFileName, bWriteHeader, tblPlanksElem, tblPlanksOther) local nSimilarCount, sCnc, i2, i, v, x, y, z, layerNum local tblMasterElems local masterElem, ownerElem local sTotPlank, sTotBoard, sTotLayer, sTotElem local globOrig, globx, globy, globz local orig, origMaster, tranMaster, tranOwner, tranTot, infoMaster, infoOwner, tblPlank local unidLayer, unidElem sTotPlank ="" sTotBoard ="" sTotLayer ="" -- Separate layers sTotElem ="" -- Composite elements parenting layers -- Loop for multilayer elements globOrig={} globOrig.x=gnOrigX globOrig.y=gnOrigY globOrig.z=gnOrigZ 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 unidLayer=500000 unidElem=600000 i=1 while tblPlanksElem and tblPlanksElem[i] do -- Calc transformation from global to master element masterElem=tblPlanksElem[i].tblMaster infoMaster=masterElem.plankinfo 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 tranMaster=ac_geo("calctranworld3", globOrig, globx, globy, globz, origMaster, infoMaster.vecx, infoMaster.vecy, infoMaster.vecz) -- Master element geometry as base sCnc = string.format( "\n\n[COMPOSITE]\n" .. "TYPE: ELEMENT\n" .. "REFERENCESIDE: SIDE:1\n" .. "SINGLEMEMBERNUMBER: %d\n" .. "ASSEMBLYNUMBER: \n" .. "ORDERNUMBER: 0\n" .. "DESIGNATION: ELEMENT %s\n" .. "ANNOTATION: \n" .. "STOREY: %d\n" .. "GROUP: %s\n" .. "STOREYTYPE: \n" .. "ELEMENTNUMBER: %s\n" .. "PACKAGE: \n" .. "MATERIAL: \n" .. "TIMBERGRADE: \n" .. "QUALITYGRADE: \n" .. "COUNT: 1\n" .. "LENGTH: %s\n" .. "HEIGHT: %s\n" .. "WIDTH: %s\n" .. "COLOUR: R:255 G:255 B:255 A:000\n" .. "UID: %d\n" .. "%s\n", unidElem, masterElem.id, masterElem.floor, masterElem.id, masterElem.id, BtlLen2Str(infoMaster.len), BtlLen2Str(infoMaster.width), BtlLen2Str(infoMaster.height), unidElem, BtlCreatePosStrElem("TRANSFORMATION", deepcopy(infoMaster), nil) ) unidElem=unidElem+1 sTotElem=sTotElem .. sCnc while tblPlanksElem[i] and tblPlanksElem[i].tblMaster==masterElem do -- Loop for layers in single multilayer element ownerElem=tblPlanksElem[i].tblOwnerElem infoOwner=ownerElem.plankinfo orig={} orig.x=infoOwner.begc.x - infoOwner.width*0.5 * infoOwner.vecy.x orig.y=infoOwner.begc.y - infoOwner.width*0.5 * infoOwner.vecy.y orig.z=infoOwner.begc.z - infoOwner.width*0.5 * infoOwner.vecy.z tranOwner=ac_geo("calctranworld3", origMaster, infoMaster.vecx, infoMaster.vecy, infoMaster.vecz, orig, infoOwner.vecx, infoOwner.vecy, infoOwner.vecz) -- Scan for element change local tblBoards, tblPlanks, nPlanks, nBoards tblBoards={} tblPlanks={} nPlanks=0 nBoards=0 while tblPlanksElem[i] and tblPlanksElem[i].tblOwnerElem==ownerElem do if tblPlanksElem[i].type==4 then nBoards=nBoards+1 tblBoards[nBoards]=tblPlanksElem[i] else nPlanks=nPlanks+1 tblPlanks[nPlanks]=tblPlanksElem[i] end i=i+1 end --ac_environment( "tolog", string.format("nBoards=%d nPlanks=%d", nBoards, nPlanks) ) -- Write all planks for current element with global coordinates gPlankTranMat=nil i2=1 while i2<=nPlanks do i2,sCnc=WriteSimilarPlanks(tblPlanks, i2, nPlanks) sTotPlank=sTotPlank .. sCnc if i2==nil then break end end if nBoards>0 then sTotBoard=sTotBoard .. BtlSortAndWriteBoards(tblBoards) end -- Element layer btl layerNum=masterElem.index-ownerElem.index -- Check if built from out to in if layerNum~=0 and gbElemLayerInToOut then layerNum=-layerNum end sCnc = string.format( "\n\n[COMPOSITE]\n" .. "TYPE: LAYER\n" .. "SINGLEMEMBERNUMBER: %d\n" .. "ASSEMBLYNUMBER: \n" .. "ORDERNUMBER: 0\n" .. "DESIGNATION: LAYER %s/%d\n" .. "LAYER: %d\n" .. "STOREYTYPE: \n" .. "ELEMENTNUMBER: %s\n" .. "PACKAGE: \n" .. "MATERIAL: \n" .. "TIMBERGRADE: \n" .. "QUALITYGRADE: \n" .. "COUNT: 1\n" .. "LENGTH: %s\n" .. "HEIGHT: %s\n" .. "WIDTH: %s\n" .. "COLOUR: R:255 G:255 B:255 A:000\n" .. "UID: %d\n" .. "%s\n", unidLayer, ownerElem.id, layerNum, layerNum, ownerElem.id, BtlLen2Str(infoOwner.len), BtlLen2Str(infoOwner.width), BtlLen2Str(infoOwner.height), unidLayer, BtlCreatePosStrElem("TRANSFORMATION", deepcopy(infoOwner), nil) ) -- Element polygon(s) local polySettings, tblPolys, kpoly, vpoly polySettings={} polySettings.tooldia=gnBvbPolyCutterDia polySettings.givelist=1 if nBoards>0 then polySettings.fromboard=1 end tblPolys=af_request("getpoly", polySettings, ownerElem.guid) for kpoly, vpoly in ipairs(tblPolys.poly) do sCnc=sCnc .. BtlGetPoly(tblPolys, vpoly, "NO", 1) -- Layer outline end gPlankTranMat=ac_geo("matmul", tranMaster, tranOwner) -- Transformation into owner elem coords if nPlanks>0 then -- Plank geometry inside the elem for i2=1,nPlanks do sCnc = sCnc .. string.format( "PROCESSKEY: 0-300-0\n" .. "%s\n" .. "PROCESSPARAMETERS: UID:%d\n", BtlCreatePosStr("REFERENCEPLANE", deepcopy(tblPlanks[i2].plankinfo), tblPlanks[i2].plateside), tblPlanks[i2].btluid ) end end if nBoards>0 then -- Board geometry inside the elem for i2=1,nBoards do local referencePlane=BtlCreatePosStrBoard("REFERENCEPLANE", deepcopy(tblBoards[i2].plankinfo), gPlankTranMat) if gbMachiningsToBoard then referencePlane=BtlCreatePosStr("REFERENCEPLANE", deepcopy(tblBoards[i2].plankinfo), gnBtlPlateSide) end sCnc = sCnc .. string.format( "PROCESSKEY: 0-300-0\n" .. "%s\n" .. "PROCESSPARAMETERS: UID:%d\n", referencePlane, tblBoards[i2].btluid ) end end local nProcessIdent nProcessIdent=1 if nBoards>0 then local s, s2, nailZ, tblDrills, nailNum -- Add layer polygon s,nProcessIdent=BtlGetSawCutObjs(ownerElem.guid, nProcessIdent) if not s and gbBtlDefSawCuts then s, nProcessIdent = BtlGetLayerPoly(af_request("getpoly", nil, ownerElem.guid), nProcessIdent) end if s then sCnc = sCnc .. s end -- And any nailings/drills in the boards tblDrills={} s2 = "" nailNum=6 s=string.lower(ownerElem.elemtypeid) if string.match(s, "^finish%a*") or string.match(s, "^panel%a*") then nailNum=5 end for i2=1,nBoards do ac_objectopen(tblBoards[i2].guid) nailZ=ac_objectget("iThickness") if layerNum>=0 then nailZ=ownerElem.thickness-nailZ end s,nProcessIdent = BtlGetNailDrillCnc( tblBoards[i2].plankinfo, gPlankTranMat, nProcessIdent, nailNum, nailZ, tblDrills ) s2 = s2 .. s ac_objectclose() end sCnc = sCnc .. s2 -- Drillings CNC local toolNumberP14=0 local arcx, arcy for i2=1,#tblDrills do v=tblDrills[i2] -- Begin point, arc from left via bottom to r s2 = string.format( "PROCESSKEY: 3-250-1 DRILLING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:%s P08:00000000 P09:%s P10:%s P11:%s P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(v.x-v.dia*0.5), BtlLen2Str(v.y), BtlInt2Str(1), BtlInt2Str(nProcessIdent+1), BtlLen2Str(v.x), BtlLen2Str(v.y-v.dia*0.5), -- P11 BtlInt2Str(2), -- P13 BtlInt2Str(toolNumberP14), -- P14 BtlInt2Str(1), -- P15 nProcessIdent ) nProcessIdent=nProcessIdent+1 -- Right point s = string.format( "PROCESSKEY: 3-250-1 DRILLING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:%s P08:%s P09:%s P10:%s P11:%s P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(v.x+v.dia*0.5), -- P01 BtlLen2Str(v.y), -- P02 BtlInt2Str(1), -- P07 BtlInt2Str(2), -- P08 BtlInt2Str(nProcessIdent+1), -- P09 BtlLen2Str(v.x), -- P10 BtlLen2Str(v.y-v.dia*0.5), -- P11 BtlInt2Str(2), -- P13 BtlInt2Str(toolNumberP14), -- P14 BtlInt2Str(1), -- P15 nProcessIdent ) s2=s2 .. s nProcessIdent=nProcessIdent+1 -- Arc from r to l s = string.format( "PROCESSKEY: 3-250-1 DRILLING\n" .. "REFERENCEPLANE: OX:00000000 OY:00000000 OZ:00000000 XX:%s XY:00000000 XZ:00000000 YX:00000000 YY:%s YZ:00000000\n" .. "PROCESSPARAMETERS:P01:%s P02:%s P03:00000000 P05:00000000 P06:00000000 P07:%s P08:%s P09:%s P10:%s P11:%s P12:00000000 P13:%s P14:%s P15:%s\n" .. "PROCESSIDENT: %d\n", BtlLen2Str(1), -- XX BtlLen2Str(1), -- YY BtlLen2Str(v.x-v.dia*0.5), -- P01 BtlLen2Str(v.y), -- P02 BtlInt2Str(1), -- P07 BtlInt2Str(2), -- P08 BtlInt2Str(0), -- P09 BtlLen2Str(v.x), -- P10 BtlLen2Str(v.y+v.dia*0.5), -- P11 BtlInt2Str(2), -- P13 BtlInt2Str(toolNumberP14), -- P14 BtlInt2Str(1), -- P15 nProcessIdent ) s2=s2 .. s nProcessIdent=nProcessIdent+1 sCnc = sCnc .. s2 end end -- And any nailings in the planks do local s, s2, nailZ, nailNum nailNum=4 s=string.lower(ownerElem.elemtypeid) if string.match(s, "^finish%a*") or string.match(s, "^panel%a*") then nailNum=5 end s2 = "" for i2=1,nPlanks do ac_objectopen(tblPlanks[i2].guid) nailZ=ownerElem.thickness if layerNum>=0 then nailZ=0 end s,nProcessIdent = BtlGetNailDrillCnc( tblPlanks[i2].plankinfo, gPlankTranMat, nProcessIdent, nailNum, nailZ, nil, true ) s2 = s2 .. s ac_objectclose() end sCnc = sCnc .. s2 end sTotLayer=sTotLayer .. sCnc -- Attach to master element sCnc = string.format( "PROCESSKEY: 0-300-0\n" .. "%s\n" .. "PROCESSPARAMETERS: UID:%d\n", BtlCreatePosStrElem("REFERENCEPLANE", deepcopy(infoOwner), tranMaster), unidLayer ) sTotElem = sTotElem .. sCnc unidLayer=unidLayer+1 end end gPlankTranMat=nil local countElem=0 local countOthers=0 if tblPlanksOther then local tblBoards, tblPlanks, nPlanks, nBoards tblBoards={} tblPlanks={} nPlanks=0 nBoards=0 i=1 while tblPlanksOther[i] do if tblPlanksOther[i].type==4 then nBoards=nBoards+1 tblBoards[nBoards]=tblPlanksOther[i] else nPlanks=nPlanks+1 tblPlanks[nPlanks]=tblPlanksOther[i] end i=i+1 end -- Write all planks for current element with global coordinates i2=1 while i2<=nPlanks do i2,sCnc=WriteSimilarPlanks(tblPlanks, i2, nPlanks) sTotPlank=sTotPlank .. sCnc if i2==nil then break end end if nBoards>0 then sTotBoard=sTotBoard .. BtlSortAndWriteBoards(tblBoards) end countOthers=countOthers+nPlanks+nBoards end countElem=0 if tblPlanksElem then countElem=#tblPlanksElem end gnStatusCount=countElem + countOthers -- Write -- Frame-kapulat -> btl if bWriteHeader then hFile=io.open(strFileName, "wt") else hFile=io.open(strFileName, "at") end io.output( hFile ) if bWriteHeader then -- btl-header... local hFile=io.open(strFileName, "at") local s local projNum = ac_environment("parsetext", "") local projName = ac_environment("parsetext", "") local custName = ac_environment("parsetext", "") s=string.format( "VERSION: \"BTL V%s\"\n" .. "BUILD: \"10400\"\n" .. "EDITION: PREFABRICATION\n" .. "[GENERAL]\n" .. "PROJECTNUMBER: \"%s\"\n" .. "PROJECTNAME: \"%s\"\n" .. "PROJECTPART: \"\"\n" .. "LISTNAME: \"ARCHIFRAME\"\n" .. "CUSTOMER: \"%s\"\n" .. "ARCHITECT: \"\"\n" .. "EDITOR: \"\"\n" .. --"DELIVERYDATE: \"0:00:00\"\n" .. --"EXPORTDATE: \"2011-12-12\"\n" .. --"EXPORTTIME: \"14:54:11\"\n" .. "EXPORTRELEASE: \"ArchiFrame BTL 2012-06-01\"\n" .. "LANGUAGE: \"EN\"\n" .. "SCALEUNIT: %d\n" .. "PROCESSINGQUALITY: AUTOMATIC\n" .. "COMPUTERNAME: \"\"\n" .. "USER: \"\"\n" .. "SOURCEFILE: \"\"\n" .. "EXPORTFILE: \"\"\n" .. "RECESS: AUTOMATIC\n" .. "COMMENT: \"\"\n", GetBtlVersionNumber(), ToBtl(projNum), ToBtl(projName), ToBtl(custName), gnBtlScaleUnit ) io.write(s) gnBtlPieceNum=1 -- Global piece number -- ...btl-header end io.write( sTotPlank ) io.write( sTotBoard ) io.write( sTotLayer ) io.write( sTotElem ) io.close( hFile ) gsCncText="" end -- ELEMENT BTL ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- BTL specific constants gnBtlStartEndOffset = 0.020 -- How much waste wood at beg & end of the piece in meters -- Feeding into machine gnBtlPlateSide=EBtlMale gnBtlLogDir=EBtlDirBeg gbBtlCalcFeed=false -- CANNOT ROTATE TO GET GEOMETRY RIGHT: If set to true, plank is fed so that longer extent lies on table (overrides previous settings) gbBtlPrintId=false -- If true, include printing ID of each plank gnBtlBestPrintSide=4 -- Depends on the feeding direction: ID and nail marks are placed on this surface gnOrigX=0 -- Origin taken from user origin at start phase gnOrigY=0 gnOrigZ=0 gnBvbPolyCutterDia=0.016 -- Diameter of the board cutter tool in the machine gbElemLayerInToOut=false gnWriteSepFiles=0 -- 0=All into single file, 1=split by owning element ID gnExpandSel=0 -- Extend selection to contain everything in the element 0/1 gnInfoDesignation=EInfoFullIDPlusUsage gnInfoMaterial=EInfoSolidOrIBeam gnInfoGrade=EInfoNone gbMarkReinforce=true gbArchiFrameBoardsPolyProcess=false gnSawCutObjSawP13=2 -- Default values before there was a setting for saw cut gnSawCutObjSawP14=0 gnSawCutObjMillP13=2 -- Default values before there was a setting for milling gnSawCutObjMillP14=0 -- Destination saws ESawDefault=1 ESawStromab=2 ESawStromabAdjGro=3 -- Need to adjust marking grooves at the end of the piece to be the same as in the middle gnDestSaw=ESawDefault gbMachiningsToBoard=false -- Whether to write plank machinings (drilling, grooves etc) to a board