You can do this with an API script. Use the command !grid to make a grid on the map layer out of drawn lines. By default, it will copy your page's existing grid information (leave the grid on for this if you have changed Cell Width): !grid --make You can specify the size of the grid (in pixels, 70 pixels == the default grid size): !grid --make --size 140 You can specify the width of the lines (default 1): !grid --make --width 3 You can specify the color: !grid --make --color #ff0000 You can provide the color with or without the #, in 3 number, 6 number, and 8 number format: !grid --make --color f00
!grid --make --color #f00
!grid --make --color ff0000
!grid --make --color #ff0000
!grid --make --color ff000099
!grid --make --color #ff000099
The last 2 digits are the transparency (00 == fully transparent, you can't see it, ff = fully opaque, you can't see through it). Upper or lower case is fine (#FF0000 == #ff0000). And you can specify all parameters together: !grid --make --color #ff000099 --size 140 --width 3 (Order doesn't matter). If you throw more things on the map and the grid is now hidden, you can pull it to the front with: !grid --front Finally, you can delete the grid on the current page with: !grid --clear Since this is just creating drawings, you can create multiple grids if you want, which might be nice for major and minor grids: Note: if you resize the page, you'll need to clear the grid and recreate it. Source code: on('ready', () => {
const API_MARK = "MakeGrid:GridLine";
const TO_FRONT_TIME = 500;
const GRID_MIN = 10;
const GRID_MAX = 1000;
const getPageForPlayer = (playerid) => {
let player = getObj('player',playerid);
if(playerIsGM(playerid)){
return player.get('lastpage');
}
let psp = Campaign().get('playerspecificpages');
if(psp[playerid]){
return psp[playerid];
}
return Campaign().get('playerpageid');
};
const getGridsOnPage = (pageid) => findObjs({
type: "path",
pageid: pageid,
layer: "map"
}).filter(o=>o.get('controlledby').split(/\s*,\s*/).includes(API_MARK));
const toFrontArrayAsync = (objs) => {
let items = [...objs];
const burndown = () => {
if( items.length) {
let i = items.shift();
toFront(i);
setTimeout(burndown, TO_FRONT_TIME);
}
};
burndown();
};
const parseColor = (color) => {
let match = color.match(/^#?([a-fA-F0-9]{3}(?:[a-fA-F0-9]{3}(?:[a-fA-F0-9]{2})?)?)$/);
if(match){
let c = match[1];
switch(c.length){
case 3:{
let cs = c.split('');
c=`${cs[0]}${cs[0]}${cs[1]}${cs[1]}${cs[2]}${cs[2]}`;
}
/* break; // intentional dropthrough */ /* falls through */
case 6:
c = `${c}99`;
/* break; // intentional dropthrough */ /* falls through */
case 8:
return `#${c}`;
}
}
};
const asAlpha = (percent) => Math.min(256,Math.max(0,Math.round(256*percent))).toString(16);
const makeGrid = (page,params,GridOffset) => {
let height = page.get('height')*70;
let width = page.get('width')*70;
let rows = Math.floor(height/GridOffset);
let cols = Math.floor(width/GridOffset);
let path = [];
for(let i = 1; i<rows; ++i){
path.push(['M',0,(i*GridOffset)]);
path.push(['L',width,(i*GridOffset)]);
}
for(let i = 1; i<cols; ++i){
path.push(['M',(i*GridOffset),0]);
path.push(['L',(i*GridOffset),height]);
}
let grid = createObj('path',{
pageid: page.id,
path: JSON.stringify(path),
fill: "transparent",
stroke: params.stroke,
rotation: 0,
layer: 'map',
stroke_width: params.stroke_width,
width: width,
height: height,
top: (height/2),
left: (width/2),
scaleX: 1,
scaleY: 1,
controlledby: params.controlledby
});
toFront(grid);
};
on('chat:message', (msg) => {
if('api'===msg.type && /^!grid/i.test(msg.content) && playerIsGM(msg.playerid)){
// !grid --clear
// !grid --make --size 70 --width 5 --color #ff000099
// !grid --front
let pageID = getPageForPlayer(msg.playerid);
let page = getObj('page',pageID);
let isMake = false;
let params = {
stroke: `${page.get('gridcolor')}${asAlpha(page.get('grid_opacity'))}`,
stroke_width: 1,
controlledby: API_MARK
};
let GridOffset = (parseInt(page.get('snapping_increment'))*70)||70;
let args = msg.content.split(/\s+--/);
args.slice(1).forEach( a => {
let cmd = a.split(/\s+/);
switch(cmd[0].toLowerCase()){
case 'clear':
getGridsOnPage(pageID).forEach(p=>p.remove());
break;
case 'front':
toFrontArrayAsync(getGridsOnPage(pageID));
break;
case 'make':
isMake = true;
break;
case 'width':
params.stroke_width = parseInt(cmd[1]) || params.stroke_width;
break;
case 'color':
params.stroke = parseColor(cmd[1]) || params.stroke;
break;
case 'size':
GridOffset = parseInt(cmd[1]) || GridOffset;
break;
}
});
if(isMake){
GridOffset = Math.min(Math.max(GridOffset,GRID_MIN),GRID_MAX);
makeGrid(page,params,GridOffset);
}
}
});
});