# [Script,Snippet] DLCircle -- Clean up Circles on the Dynamic Lighting layer so they look right and work right. 1506541082

Edited 1506629152
The Aaron
Forum Champion
API Scripter
Circles are a problem because their representation is interpreted as a line, rather than 4 Bézier curves. Here's a script that fixes all the circles on the DL layer by approximating them with polygons as you draw them. &nbsp;The number of line segments used is based on the size of the circle, but will be a minimum of 12 and a maximum of 196 (but it would need to be a huge circle!), approximately 2-3 every grid square: You can also fix existing circles on the Dynamic Lighting Layer by running: !dlcircle-fix Sometimes circles (or paths in general) can end up corrupted by scripts that are generating them. &nbsp;If you happen to run into that circumstance, you can run: !dlcircle-fix-clean And it will remove the broken ones (and will say (cleaned) instead of (skipped) ). Here's the script: on('ready',()=&gt;{ &nbsp; &nbsp; const circleKey=['M','C','C','C','C']; &nbsp; &nbsp; const simpleObject = (o)=&gt;JSON.parse(JSON.stringify(o)); &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; const approximateCircle = (w,h) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const w2=w/2; &nbsp; &nbsp; &nbsp; &nbsp; const h2=h/2; &nbsp; &nbsp; &nbsp; &nbsp; const at = (theta) =&gt; ({x: Math.cos(theta)*w2, y: Math.sin(theta)*h2});&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const steps = Math.min(Math.max(Math.round( (Math.PI*2*Math.sqrt((w2*w2+h2*h2)/2))/35),4),50); &nbsp; &nbsp; &nbsp; &nbsp; const stepSize = Math.PI/(2*steps); &nbsp; &nbsp; &nbsp; &nbsp; let acc=[[],[],[],[]]; &nbsp; &nbsp; &nbsp; &nbsp; let th=0; &nbsp; &nbsp; &nbsp; &nbsp; _.times(steps+1,()=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let pt=at(th); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; acc.push([pt.x,pt.y]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; acc.push([-pt.x,pt.y]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; acc.push([-pt.x,-pt.y]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; acc.push([pt.x,-pt.y]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; th+=stepSize; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; acc = acc.concat( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; acc.reverse().slice(1), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; acc.slice(1), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; acc.reverse().slice(1) &nbsp; &nbsp; &nbsp; &nbsp; ); &nbsp; &nbsp; &nbsp; &nbsp; return JSON.stringify(acc.map((v,i)=&gt;([(i?'L':'M'),w2+v,h2+v]))); &nbsp; &nbsp; }; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; const buildCircle = (p) =&gt; Object.assign( &nbsp; &nbsp; &nbsp; &nbsp; simpleObject(p), &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _path: approximateCircle( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p.get('width'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p.get('height') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ) &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; ); &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; on('add:path',(p)=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; if(p.get('layer')==='walls'){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let path = JSON.parse(p.get('path')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.pluck(path,0).every((v,i)=&gt;v===circleKey[i])){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; createObj('path',buildCircle(p)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p.remove(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; on('chat:message',(msg)=&gt;{ &nbsp; &nbsp; &nbsp; &nbsp; if('api'===msg.type && /^!dlcircle-fix\b/i.test(msg.content) && playerIsGM(msg.playerid)){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let clean = /^!dlcircle-fix-clean\b/i.test(msg.content), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fixed = 0, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; invalid = 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; findObjs({type:'path', layer:'walls'}).forEach((p) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let path = JSON.parse(p.get('path')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.pluck(path,0).every((v,i)=&gt;v===circleKey[i])){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; createObj('path',buildCircle(p)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p.remove(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fixed++; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } catch (e) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; invalid++; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(clean){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p.remove(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat('DLCircle',`/w gm Fixed \${fixed} circle\${1===fixed?'':'s'}, \${invalid} invalid circle\${1===invalid?'':'s'} \${clean?'(cleaned)':'(skipped)'}`); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); }); Support my work on If you use my scripts, want to contribute, and have the spare bucks to do so , go right ahead. However, please don't feel like you must contribute just to use them! I'd much rather have happy Roll20 users armed with my scripts than people not using them out of some sense of shame. Use them and be happy, completely guilt-free! Disclaimer: This Patreon campaign is not affiliated with Roll20; as such, contributions are voluntary and Roll20 cannot provide support or refunds for contributions. 1506545073
:-D 1506646462

Edited 1506653753
Andrew C
Pro
Marketplace Creator
Hey Aaron, As a stupid thought, would you be able to update Walls.API to use the same functionality or to have this built into it?