Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Doubling the size of a map

1607428423

Edited 1607428582
Since the maps for Rime of the Icemaiden produced by roll20 have a lot of them set to have 0.5 for the "Cell Width", I am trying to rescale them so that Cell Width can be set to 1. This is important so that the nameplates under the tokens do not overlay the entire square below them!  It also is required so that 100% zoom shows all tokens are the usual 70 pixel size. Does anyone know how to scale everything on the map in a quick manner? My current method, which is quite tediously slow: 1) double the size of the page 2) double the size of the map on the map layer 3) select all tokens and try to drag the selected rectangle to make all the tokens double size and place them correctly, but this is very prone to error. 4) select everything on the GM layer and try stretching that selection rectangle - again, guesswork is required 5) select everything on the lighting layer, and try stretching that to place the resulting lighting as best as possible over the new size map. As you can see, steps 3-5 are not perfect since the selection rectangle only covers the area of the tokens, not the entire page. I would like to know how to scale EVERYTHING in one go. (Of course, if roll20 handled nameplate size properly, or published maps with 5 ft squares properly, then this wouldn't be required.)
1607433083
The Aaron
Roll20 Production Team
API Scripter
I think I have a script (or most of one) that does this. I'll take a look when I get firm to my computer. 
1607436949
The Aaron
Roll20 Production Team
API Scripter
I found it.  It's about 90% done.  I'll see if I can finish it up (just has a few bugs with positioning graphics) and post it here.
That would be great! Thanks The Aaron
1607441719
The Aaron
Roll20 Production Team
API Scripter
Ok, here's a first pass version of it.  It uses the Grid Scale Distance setting on the page to rescale everything.  On some maps, that's not set correctly (for example, Duergar Outpost is set to 5), so you might needs to fix that before it will run.  Just make sure if the map says "1 square = 10 ft" that Grid Scale Distance says 10 in the page settings. You then just run: !nps There is also a command to do all the maps in one go, but I don't know that I'd trust it enough for that yet. =D  I suggest making use of the new Duplicate Page functionality so you can test it out without screwing up your originals. Here's the code: on('ready',() => { const s = { gmNote: `margin-left: -40px; border: 1px solid #ccc; border-radius: .5em; padding: .1em .5em; background-color: #eee; font-size: 10px; font-weight: bold;` }; const scgm=(msg)=>{ sendChat('NPS',`/w gm <div style="${s.gmNote}"> ${msg} </div> `); }; const scaleGraphic = (scale) => (graphic) => { graphic.set({ width: graphic.get('width')*scale, height: graphic.get('height')*scale, left: graphic.get('left')*scale, top: graphic.get('top')*scale }); }; const repositionGraphic = (scale) => (graphic) => { graphic.set({ width: graphic.get('width')*scale, height: graphic.get('height')*scale, left: graphic.get('left')*scale, top: graphic.get('top')*scale }); }; const scaleText = (scale) => { return (text) => { text.set({ left: text.get('left')*scale, top: text.get('top')*scale, font_size: text.get('font_size')*scale }); }; }; const simpleObject = (o) => JSON.parse(JSON.stringify(o)); const scalePathString = (pathstring,scale) => { return JSON.stringify(_.map(JSON.parse(pathstring),(n)=> _.map(n,(i)=> _.isNumber(i) ? scale*i : i ))); }; const scaleDrawing = (scale) => { return (drawing) => { let newpath=_.omit(simpleObject(drawing),['_id','_type']); if(_.contains(['','[]'],newpath._path)){ return; } newpath.path=scalePathString(newpath._path,scale); delete newpath._path; newpath.top*=scale; newpath.left*=scale; newpath.width*=scale; newpath.height*=scale; /*let newPathObj =*/ createObj('path',newpath); drawing.remove(); }; }; const adjustPages = (pages) => { let page=pages.shift(), scale=page.get('scale_number')/5; let mapGraphics = findObjs({ pageid: page.id, type: 'graphic', subtype: 'token', represents: '' }); let mgids = mapGraphics.map(g=>g.id); let otherGraphics = findObjs({ pageid: page.id, type: 'graphic' }) .filter(o=> !mgids.includes(o.id) && o.get('subtype')!=='card' ); let allText = filterObjs((o)=>{ return o.get('pageid')===page.id && o.get('type')==='text'; }); let allDrawings = filterObjs((o)=>{ return o.get('pageid')===page.id && o.get('type')==='path'; }); otherGraphics=_.without(otherGraphics,mapGraphics); scgm(`Adjusting page: <u>${page.get('name')}</u>`); // scale page scgm(`-- Scale from ${page.get('scale_number')}ft to 5ft (x${scale} size)`); page.set({ width: page.get('width')*scale, height: page.get('height')*scale, scale_number: 5 }); // scale all graphics on the map layer scgm(`-- Scaling map images: ${mapGraphics.length}`); _.each(mapGraphics,scaleGraphic(scale)); // reposition all graphics on the other layers scgm(`-- Repositioning other layer images: ${otherGraphics.length}`); _.each(otherGraphics,repositionGraphic(scale)); // reposition and scale all text on all layers scgm(`-- Scaling and Repositioning text: ${allText.length}`); _.each(allText,scaleText(scale)); // redraw all drawings scgm(`-- Redrawing paths to scale: ${allDrawings.length}`); _.each(allDrawings,scaleDrawing(scale)); if(pages.length){ _.delay(adjustPages,100,pages); } }; on('chat:message',(msg) => { let cmds,pages,player; if('api' === msg.type && msg.content.match(/^!nps(?:-all)?\b/) && playerIsGM(msg.playerid)){ cmds=msg.content.split(/\s+/); player=getObj('player',msg.playerid); switch(cmds.shift()){ case '!nps-all': pages=filterObjs((o)=>{ return o.get('type')==='page' && o.get('scale_units')==='ft' && o.get('scale_number')>5 ; }); /* falls through */ case '!nps': pages = pages || filterObjs((o)=>{ return o.get('type')==='page' && o.get('scale_units')==='ft' && o.get('scale_number')>5 && o.id === player.get('lastpage'); }); if(pages.length){ scgm( `Operating on pages: <ul><li>${_.map(pages,(p)=>p.get('name')).join('</li> <li>')}</li></ul>`); _.delay(adjustPages,50,pages); } else { scgm( `No work to do on the specified pages.`); } break; } } }); });
1607441836
The Aaron
Roll20 Production Team
API Scripter
One other note: I never finished this because there really should be a choice of if you're going to scale tokens or not.  originally, I was leaning toward not scaling them (there are some maps in Storm King's Thunder where that wouldn't make sense), but this seems like they should be scaled.  This version of the script scales both map images and token images (but not cards).
I just tested this on a LMoP map and it worked beautifully!
1607457007
The Aaron
Roll20 Production Team
API Scripter
Oh good!  Probably I should polish it up and put it in the 1-click. 
1607469824
Gold
Forum Champion
Very excellent help API, for a very understandable DM need/preference. Personally I like using .5 grid scale often, but I acknowledge that in many-most cases the 1:1 grid scale is the most practical for VTT, so I would probably consider doing the same as Farling if I was running one of those modules.
1607507883

Edited 1607530213
I'm testing it now. I found that I needed to set both the grid to 10 (from 5) and the cell scaling back to 1 (from 0.5) for each of the affected maps. But after making this change, all relevant maps have been successfully converted. Thanks again Aaron, excellent work!!!
1607535199
The Aaron
Roll20 Production Team
API Scripter
Ah, that's great feedback!  I can probably divide grid by scaling to get the apparent scale, and then adjust from that.  I'll give that a try later. Thank you for letting me know!