gnCncErrCount=0 function AddCncErr(pos, text) if gnCncErrCount==0 then gTblCncErr={} end gnCncErrCount=gnCncErrCount+1 gTblCncErr[gnCncErrCount] = {} gTblCncErr[gnCncErrCount].guid=gsCurrentLogGuid gTblCncErr[gnCncErrCount].logid=gsCurrentLogId gTblCncErr[gnCncErrCount].pos=pos gTblCncErr[gnCncErrCount].text="[SalvadorCNC] "..text end -- Supported machining IDs EMcFrAngledBeg = 101 EMcFrAngledEnd = 201 -- Cut side ECutSideInvalid = -1 -- Non straight cuts ECutSideFrontBack = 0 ECutSideTopBottom = 1 EPS=0.00001 extension="ASC" --------------- Called by ArchiFrame ------------------ -------------------------------------------------------- function OnInit(plnFileName) gnCncErrCount=0 local projectName = ac_environment("parsetext", "") if projectName==nil or #projectName==0 then return plnFileName, extension end return projectName, extension end function OnSaveList(filePath) local programs={} local programCount=0 for _, GUID in ipairs(gTblPlanks) do ac_objectopen(GUID) local plankWidth, plankHeight, plankLength=GetPlankDimensions() local cutSide=nil local begMcs={} local endMcs={} -- Collect angled cut machinings from every plank for mcIdx=1, ac_objectget("iMc", -1)-1 do local id=ac_objectget("iMc", mcIdx, 1) local x=ac_objectget("iMc", mcIdx, 4) if id==EMcFrAngledBeg or id==EMcFrAngledEnd then local cutAngle=ac_objectget("iMc", mcIdx, 2) local tiltAngle=ac_objectget("iMc", mcIdx, 3) local nextCutSide=GetCutSide(cutAngle, tiltAngle) if id==EMcFrAngledEnd then x=plankLength-x end if nextCutSide==ECutSideInvalid then AddCncErr(x, string.format("Angle or tilt angle is not straight (%s), skipping", FormatAngledCut(id, cutAngle, tiltAngle))) elseif cutSide~= nil and cutSide~=nextCutSide then AddCncErr(x, string.format("Cuts in both directions is not supported (%s), skipping", FormatAngledCut(id, cutAngle, tiltAngle))) else -- First cut determines plank cut side if cutSide==nil then cutSide=nextCutSide if cutSide==ECutSideFrontBack then plankWidth, plankHeight=plankHeight, plankWidth end end local y=ac_objectget("iMc", mcIdx, 6)+plankWidth/2 local angle=cutAngle if cutSide==ECutSideFrontBack then angle=tiltAngle y=ac_objectget("iMc", mcIdx, 5) end local mcs=begMcs if id==EMcFrAngledEnd then angle=180-angle mcs=endMcs end table.insert(mcs, {angle=angle, x=x, y=y}) end else AddCncErr(x, string.format("Unsupported machining (id=%d), skipping", id)) end end -- Calculate tail cuts table.insert(endMcs, {angle=0, x=0, y=0 }) table.insert(endMcs, {angle=90, x=plankLength, y=0 }) table.insert(endMcs, {angle=180, x=0, y=plankWidth }) table.sort(endMcs, function (mcA, mcB) return mcA.angle > mcB.angle end) plankLength=plankLength-(plankLength-CalcAngledMcsIntersectionPt(endMcs[1], endMcs[2]).x) local tailCuts=CalculateCuts(endMcs, false) -- Calculate head cuts table.insert(begMcs, {angle=0, x=0, y=plankWidth }) table.insert(begMcs, {angle=90, x=0, y=0 }) table.insert(begMcs, {angle=180, x=0, y=0 }) table.sort(begMcs, function (mcA, mcB) return mcA.angle < mcB.angle end) plankLength=plankLength-CalcAngledMcsIntersectionPt(begMcs[1], begMcs[2]).x local headCuts=CalculateCuts(begMcs, true) local plankWidthMM=Round(MToMM(plankWidth), 0) if programs[plankWidthMM]==nil then programs[plankWidthMM]={} programCount=programCount+1 end -- Group planks with the same width into the same program table.insert(programs[plankWidthMM], { headCuts=headCuts, tailCuts=tailCuts, length=Round(MToMM(plankLength), 0), piecesToCut=1 }) ac_objectclose() end if programCount==1 then local width, program=next(programs) -- Gets the first element in the table local file=io.open(filePath, "w") io.output(file) io.write(AscFormatProgram(width, program)) io.close(file) elseif programCount>1 then -- For multiple programs insert the width of the program to the file path -- Example: -- -- filePath: 'test.ASC' -- 3 programs: width=50mm, width=51mm, width=52mm -- -- The generated files will be: test_W50.ASC, test_W51.ASC and test_W52.ASC filePath=filePath:sub(1, -(#extension+2)) for width, program in pairs(programs) do local filePathWidthFormated = string.format("%s_W%d.%s", filePath, width, extension) local file=io.open(filePathWidthFormated, "w") io.output(file) io.write(AscFormatProgram(width, program)) io.close(file) end end end ------------- Algo helpers ---------------- ------------------------------------------ function GetCutSide(angle, tiltAngle) if angle==90.0 then return ECutSideFrontBack end if tiltAngle==90.0 then return ECutSideTopBottom end return ECutSideInvalid end function CalculateCuts(mcs, flip) local cuts={} for i=2,#mcs-1 do local pt1=CalcAngledMcsIntersectionPt(mcs[i-1], mcs[i]) local pt2=CalcAngledMcsIntersectionPt(mcs[i], mcs[i+1]) local quote=math.abs(pt2.x-pt1.x) if quote==0 then quote=math.abs(pt2.y-pt1.y) end if quote>EPS then local angle=TernaryIf(flip, 180-mcs[i].angle, mcs[i].angle) table.insert(cuts, { angle=Round(angle, 1), quote=Round(MToMM(quote), 1) }) end end cuts[#cuts].quote=0 return cuts end function GetPlankDimensions() local info=af_request ("plankinfo") return info.width, info.height, info.len end function Line(x1, y1, x2, y2) return { pt1={x=x1, y=y1}, pt2={x=x2, y=y2} } end function LineFromAngleAndPt(angle, x, y) local rad=math.rad(angle) return Line( x-math.cos(rad)*0.1, y-math.sin(rad)*0.1, x+math.cos(rad)*0.1, y+math.sin(rad)*0.1 ) end function CalcAngledMcsIntersectionPt(machiningA, machiningB) local lineA=LineFromAngleAndPt(machiningA.angle, machiningA.x, machiningA.y) local lineB=LineFromAngleAndPt(machiningB.angle, machiningB.x, machiningB.y) return LineX(lineA, lineB) end function LineX(lineA, lineB) local x, y=ac_geo ( "linex", lineA.pt1.x, lineA.pt1.y, lineA.pt2.x, lineA.pt2.y, lineB.pt1.x, lineB.pt1.y, lineB.pt2.x, lineB.pt2.y ) return { x=x, y=y } end ------------------------------------------------ ---------- Asc formatting functions ----------- function AscFormatProgram(widthMM, program) local text="[GENERAL]\n" text=text.."ProgDes=ARCHIFRAME EXPORT\n\n" text=text.."[PARLENW01Q01]\n" text=text..string.format("Par021=%d\n\n", widthMM) text=text.."[LENGHTSW01Q01]\n" for i, length in ipairs(program) do text=text..AscFormatLength(i, length) end return text end function AscFormatLength(i, length) return string.format( "Len%03d=%d, %d, %d, %d, %d, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %d, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %0.1f, %d, %d, %d, %d, %d, %d, %s\n", i, -- a1-a5 ValueOr(length.exclusion, 0), ValueOr(length.fixedPriority, 0), ValueOr(length.length, 0), 17, -- Required magic number ValueOr(length.fix2, 0), -- b1-b10 ValueOr(length.headCuts[1], {angle=90}).angle, ValueOr(length.headCuts[1], {quote=0}).quote, ValueOr(length.headCuts[2], {angle=0}).angle, ValueOr(length.headCuts[2], {quote=0}).quote, ValueOr(length.headCuts[3], {angle=0}).angle, ValueOr(length.headCuts[3], {quote=0}).quote, ValueOr(length.headCuts[4], {angle=0}).angle, ValueOr(length.headCuts[4], {quote=0}).quote, ValueOr(length.headCuts[5], {angle=0}).angle, ValueOr(length.headCuts[5], {quote=0}).quote, -- a7 ValueOr(length.fix3, 0), -- c1-c10 ValueOr(length.tailCuts[1], {angle=90}).angle, ValueOr(length.tailCuts[1], {quote=0}).quote, ValueOr(length.tailCuts[2], {angle=0}).angle, ValueOr(length.tailCuts[2], {quote=0}).quote, ValueOr(length.tailCuts[3], {angle=0}).angle, ValueOr(length.tailCuts[3], {quote=0}).quote, ValueOr(length.tailCuts[4], {angle=0}).angle, ValueOr(length.tailCuts[4], {quote=0}).quote, ValueOr(length.tailCuts[5], {angle=0}).angle, ValueOr(length.tailCuts[5], {quote=0}).quote, -- a7-a11 ValueOr(length.piecesToCut, 0), ValueOr(length.donePieces, 0), ValueOr(length.ejectorNumber, 0), ValueOr(length.freeSpecification, 0), ValueOr(length.value, 0), ValueOr(length.label, "") ) end ------------------------------------ ------------ Utilities -------------- function ValueOr(value, default) return TernaryIf(value==nil, default, value) end function TernaryIf(cond, T, F) if cond then return T else return F end end function Round(number, toDecimalPlaces) mult=10^toDecimalPlaces return math.floor(number*mult+0.5)/mult end function MToMM(m) return m*1000.0 end function FormatAngledCut(id, angle, tiltAngle) local pos="unknown" if id==EMcFrAngledBeg then pos="beg" elseif id==EMcFrAngledEnd then pos="end" end return string.format("pos=%s, angle=%0.2f, tilt angle=%0.2f", pos, angle, tiltAngle) end ------------------------------------- ------------ Debugging -------------- function DbgLine(line) AddCncErr(0, string.format("Line: x1=%0.2f, y1=%0.2f, x2=%0.2f, y2=%0.2f", MToMM(line.x1), MToMM(line.y1), MToMM(line.x2), MToMM(line.y2))) end function DbgCut(cut) AddCncErr(0, string.format("CUT: ang=%0.2f, x=%0.2f, y=%0.2f", cut.angle, cut.x, cut.y)) end function Dump(o) if type(o) == 'table' then local s = '{ ' for k,v in pairs(o) do if type(k) ~= 'number' then k = '"'..k..'"' end s = s .. '['..k..'] = ' .. Dump(v) .. ',' end return s .. '} ' else return tostring(o) end end