local util = require('util')
local apiSync = require('apiSync')
app.on('init', function()
app.plugin.openbrush.api = apiSync.init( 'openbrush' )
end)
local function scanTiltFile(exp)
if exp.path:lower():match(".tilt$") then
app.markViewable(exp)
end
end
local function scanForOpenBrush(exp,opts)
local tags = app.plugin.openbrush.getTags()
foreach( tags, function(k,tag)
tag = tag:gsub("#","")
if exp.url:match( app.plugin.openbrush.pattern.collection(tag) ) then -- collection
app.plugin.janusxr.setLayout( exp, app.opts.plugin_openbrush_layout, {
room = app.opts[ "plugin_openbrush_wrap_room_" .. app.opts.plugin_openbrush_layout ] or 'empty',
portal_width = tonumber( app.opts.plugin_openbrush_portal_width ),
grid_size = tonumber( app.opts.plugin_openbrush_grid_size )
})
end
end)
scanTiltFile(exp)
end
app.on('scan_experience', function(exp) scanForOpenBrush(exp,{dir=true}) end)
app.on('scan_experience_janusxr', function(opts)
local exp = opts.exp
if exp then
local tags = app.plugin.openbrush.getTags()
foreach( tags, function(k,tag)
tag = tag:gsub("#","")
if exp.url:match( app.plugin.openbrush.pattern.assetdir(tag) ) then -- assetdir
app.markViewable(exp)
end
if exp.url:match( app.plugin.openbrush.pattern.assetfile(tag) ) then -- assetfile
-- disable janusxr room wrapping (if any)
exp.janusxr = nil
-- also for the parent, and redirect to indirect child asset
if exp.parent.layout == nil and exp.parent.janusxr then
exp.parent.janusxr = nil
exp.parent.redirect = exp.url
end
end
end)
end
end)
app.on('set', function(k,v)
end)
local sync_URL = app.opts_plugin_openbrush_sync_URL or ''
-- this registers the plugin UI
local plugin = {
name = "OpenBrush",
infowide = true,
info = [[
OpenBrush lets you paint in 3D space with virtual reality.
XRForge's webviewer (JanusWeb) supports interconnecting OpenBrush projects via 3D portals or a grid.
OpenBrush projects are stored in a (selfhosted) icosa.gallery instance, which can be configured below.
NOTE: for collective virtualworld building use hashtags in project-titles. Each hashtag will become its own folder, which allows for crowdsourcing/combining scenes designed in openbrush.
]],
--buttons = {
-- { label = "resync now!", href="/reset" }
--},
opts = {
{
key='app.opts.plugin_openbrush_layout',
title='Layout assets',
info='NOTE: use #overlay in asset-title to skip layout (sky/floor/e.g.)',
default="grid",
values=app.plugin.global.getLayouts()
},
{
key='app.opts.plugin_openbrush_wrap_room_portals',
title='grid: wrap',
info='render 3D files in janusxr.xml room',
default="room_plane",
values= app.plugin.janusxr.opts[4].values -- reuse rooms from janusxr plugin
},
{
key='app.opts.plugin_openbrush_wrap_room_grid',
title='portals: wrap',
info='render 3D files in janusxr.xml room',
default="empty_floor",
values= app.plugin.janusxr.opts[4].values -- reuse rooms from janusxr plugin
},
{
key='app.opts.plugin_openbrush_layout_grid_size',
title='Grid size',
info='in square meters (only used for grid layout)',
default="74",
placeholder="74",
values=''
},
{
key='app.opts.plugin_openbrush_sync_strategy',
title='How to sync',
info='download assets locally (prevent linkrot)',
default = "mirror",
values={mirror="mirror locally"}
},
{
key='app.opts.plugin_openbrush_sync_when',
title='Sync interval',
info='when to sync?',
default = "boot",
values={disabled="disabled", boot="on boot", hourly="hourly", daily="daily"}
},
{
key='app.opts.plugin_openbrush_sync_folder',
title='Sync to folder',
info='openbrush (icosa.gallery) assets will be saved here',
default = "./myverse/tronverse",
type = "dir",
values = {} -- populated by plugin/diskscan.lua
},
{
key='app.opts.plugin_openbrush_sync_folder_thumbnail',
title='Thumbnail',
info='URL of Thumbnail for folder/portal',
placeholder = "img/openbrush.png",
default = "img/openbrush.png",
values=''
},
{
key='app.opts.plugin_openbrush_portal_width',
title='Portal spacing',
info='Spacing between portals (prevent thumbnail-overlap)',
placeholder = "4.2",
default = "4.2",
values=''
},
{
key='app.opts.plugin_openbrush_sync_URL',
title='Icosa gallery URL',
info='public/selfhosted icosa.gallery server-instance',
placeholder = "icosa.gallery",
default = "icosa.gallery",
values=''
},
{
key='app.opts.plugin_openbrush_sync_hashtag',
title='tags',
info=( sync_URL and 'space-separated tags will generate their own folders' or ''),
default = "#tronverse-icosagallery",
placeholder = "#tronverse-icosagallery",
values=''
},
{
key='app.opts.plugin_openbrush_sync_category',
title='category',
info=( sync_URL and 'space-separated, leave empty or see API docs' or ''),
placeholder = "ART",
default = "",
values=''
},
{
key='app.opts.plugin_openbrush_sync_keywords',
title='keywords',
info=( sync_URL and 'space-separated, leave empty or see API docs' or ''),
placeholder = "xrforge",
default = "",
values=''
},
{
key='app.opts.plugin_openbrush_sync_triangle_max',
title='Polygons maximum',
info=( sync_URL and 'performance-cap, leave empty or see API docs' or ''),
placeholder = "10000",
default = "",
values=''
},
{
key='app.opts.plugin_openbrush_sync_license',
title='License',
info='filter assets on specific license',
default = "",
values={ [""]="", ALL_CC="CreateCommons (ALL)"}
},
{
key='app.opts.plugin_openbrush_sync_author',
title='Author name',
info='space-separated: filter assets on specific Author name',
placeholder='sarah',
default = "",
values=""
},
{
key='app.opts.plugin_openbrush_sync_authorid',
title='Author id',
info='space-separated: filter assets on specific Author id',
placeholder='9e8b98fbe',
default = "",
values=""
},
{
key='app.opts.plugin_openbrush_sync_maxitems',
title='Max latest items',
info='limit the total amount of result',
placeholder='8',
default = "8",
values=""
},
{
key='app.opts.plugin_openbrush_sync_collection',
title='In collection',
info='should assets be part of a collection?',
default = "disabled",
values={enabled="yes", disabled="no"}
}
},
pattern = {
collection = function(tag) return '/' .. tag:gsub("-","%%-") .. '$' end,
assetfile = function(tag) return '/' .. tag:gsub("-","%%-") .. '/' end,
assetdir = function(tag) return '/' .. tag:gsub("-","%%-") .. '/.*[^/]$' end,
},
getTags = function()
local tags = app.plugin.openbrush.tags
if not tags then
tags = util.split( app.opts.plugin_openbrush_sync_hashtag, ' ')
table.insert( tags,"openbrush")
app.plugin.openbrush.tags = tags
end
return tags
end,
-- expected by src/.lua/apiSync.lua
apiCall = function(tag)
return 'https://api.' .. app.opts.plugin_openbrush_sync_URL .. '/v1/assets?xrforge=' .. EscapeFragment(app.host)
.. '&xrforge_strategy=' .. EscapeFragment(app.opts.plugin_openbrush_sync_strategy)
.. '&time=' .. tostring( os.time() )
.. (tag and #tag > 0 and '&keywords=' .. EscapeFragment(tag) or '')
.. (#app.opts.plugin_openbrush_sync_category > 0 and '&category=' .. EscapeFragment(app.opts.plugin_openbrush_sync_category) or '')
.. (#app.opts.plugin_openbrush_sync_keywords > 0 and '&keywords=' .. EscapeFragment(app.opts.plugin_openbrush_sync_keywords) or '')
.. (#app.opts.plugin_openbrush_sync_triangle_max > 0 and '&triangleCountMax=' .. EscapeFragment(app.opts.plugin_openbrush_sync_triangle_max) or '')
.. (#app.opts.plugin_openbrush_sync_license > 0 and '&license=' .. app.opts.plugin_openbrush_sync_license or '' )
.. (app.opts.plugin_openbrush_sync_collection == 'enabled' and '&inCollection=true' or '' )
.. (#app.opts.plugin_openbrush_sync_maxitems > 0 and '&pageSize=' .. EscapeFragment(app.opts.plugin_openbrush_sync_maxitems) or '' )
-- TODO: filter AFTER the request
-- .. (#app.opts.plugin_openbrush_sync_author > 0 and '&authorName=' .. EscapeFragment(app.opts.plugin_openbrush_sync_author) or '')
-- .. (#app.opts.plugin_openbrush_sync_authorid > 0 and '&authorId=' .. EscapeFragment(app.opts.plugin_openbrush_sync_authorid) or '')
end,
getFolder = function(tag)
local folder = app.opts.plugin_openbrush_sync_folder
if tag then
folder = folder .. '/' .. util.hyphenate(tag)
end
return folder
end,
-- expected by src/.lua/apiSync.lua
fetch = function(url,opts)
opts = opts or {}
if not opts.body then
local ok, headers, body = Fetch( url )
if not ok then
return print("[openbrush.lua] error: could not fetch: " .. app.plugin.openbrush.apiCall(tag))
end
opts.body = body
end
return DecodeJson(opts.body)
end,
ensureREADME = function(tag,file)
if not util.fileExist( file ) then
util.writefile( file, app.opts.plugin_global_popup_instructions .. '\n'
.. [[This virtual world is generated in realtime via icosa.gallery.
You can add/create parcels in icosa.gallery, by publishing via OpenBrush, OpenBlocks, or Blender.
> NOTE: Make sure to add hashtag **]] .. tag .. [[** to the title (and re-run xrforge for fast re-sync). ]]
)
end
end,
-- expected by src/.lua/apiSync.lua
download = function(t,tag)
local me = app.plugin.openbrush
local synced = 0
foreach( t.assets, function(k,asset)
if type(asset) == 'table' then
local dirname = '_' .. asset.assetId .. '-' .. util.hyphenate( asset.displayName)
local dirnameFull = me.getFolder(tag) .. '/' .. dirname
local jsonfile = dirnameFull .. '/asset.json'
local README = me.getFolder(tag) .. '/README.md'
local sync = true
local assetPrevious = false
local f = false
function preferFormat(f)
local found = false
foreach( asset.formats, function(k,v)
if v.formatType == f then
found = v
end
end)
return found
end
f = preferFormat('TILT')
if not f then f = preferFormat('GLTF2') end
if not f then f = preferFormat('GLTF1') end
if not f then f = preferFormat('OBJ') end
if f then
if not util.fileExist( dirnameFull ) then
os.execute("mkdir -p " .. dirnameFull)
end
me.ensureREADME(tag,README)
if util.fileExist(jsonfile) then
assetPrevious = DecodeJson( util.readfile(jsonfile) )
if util.parseIsoDate(asset.updateTime) == util.parseIsoDate(assetPrevious.updateTime) then
sync = false -- already finished
end
end
if sync then
print('â
' .. asset.displayName .. ' (syncing) #openbrush')
util.writefile( jsonfile, EncodeJson(asset) )
local rootfile = dirnameFull .. '/' .. f.root.url:gsub(".*/","")
local thumbnailfile = ''
if asset.thumbnail and asset.thumbnail.url then
thumbnailfile = dirnameFull .. '.' .. asset.thumbnail.url:gsub(".*%.",'')
print('đ¤ fetch ' .. asset.thumbnail.url)
os.execute('wget "' ..asset.thumbnail.url .. '" -O "' .. thumbnailfile .. '"')
end
print('đ¤ fetch ' .. f.root.url)
os.execute('wget "' .. f.root.url .. '" -O "' .. rootfile .. '"')
if #f.resources > 0 then
foreach( f.resources, function(k,resource)
local file = dirnameFull .. '/' .. resource.url:gsub(".*/","")
print('đ¤ fetch ' .. resource.url)
os.execute('wget "' .. resource.url .. '" -O "' .. file .. '"')
end)
end
synced = synced + 1
else
print('âšī¸ ' .. asset.displayName .. ' (up to date) #openbrush')
end
else
print('[openbrush.lua] error: no suitable format found for ' .. asset.displayName .. ' ' .. asset.assetId)
end
end
end, true)
return synced
end
}
if not util.cmdExist('wget') then
plugin.info = plugin.info .. '
NOTE: wget command not installed..plugin is disabled'
end
return plugin