365 lines
14 KiB
Lua
365 lines
14 KiB
Lua
local util = require('util')
|
||
local apiSync = require('apiSync')
|
||
|
||
app.on('init', function()
|
||
if not app.opts.plugin_diskscan_dir:match("^/zip/") then
|
||
app.plugin.openbrush.api = apiSync.init( 'openbrush' )
|
||
else
|
||
print("[openbrush.lua] disabled..please create a 'myverse' dir we can store files into")
|
||
end
|
||
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 = [[
|
||
<img src="img/openbrush.png" style="width:50%; max-width:233px; display:inline-block; float:left; margin-right:20px;"/>
|
||
<a href="https://openbrush.app/" target="_blank">OpenBrush</a> lets you paint in 3D space with virtual reality.<br>
|
||
XRForge's webviewer (<a href="https://github.com/jbaicoianu/janusweb">JanusWeb</a>) supports interconnecting OpenBrush projects via 3D portals or a grid.<br/>
|
||
OpenBrush projects are stored in a (selfhosted) <a href="https://icosa.gallery" target="_blank">icosa.gallery</a> instance, which can be configured below.<br><br>
|
||
<b>NOTE:</b> for collective virtualworld building use <b>hashtags in project-titles</b>. Each hashtag will become its own folder, which allows for <a href="/about#autocompose">crowdsourcing/combining scenes</a> 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 <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> 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 <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> 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/tech-demos/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 <a href="https://github.com/icosa-foundation/icosa-gallery target="_blank">icosa.gallery</a> 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 <a href="https://api.' .. sync_URL .. '/v1/docs#/Assets/icosa_api_assets_get_assets">API docs</a>' or ''),
|
||
placeholder = "ART",
|
||
default = "",
|
||
values=''
|
||
},
|
||
{
|
||
key='app.opts.plugin_openbrush_sync_keywords',
|
||
title='keywords',
|
||
info=( sync_URL and 'space-separated, leave empty or see <a href="https://api.' .. sync_URL .. '/v1/docs#/Assets/icosa_api_assets_get_assets">API docs</a>' 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 <a href="https://api.' .. sync_URL .. '/v1/docs#/Assets/icosa_api_assets_get_assets">API docs</a>' 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 <a href='https://icosa.gallery' target='_blank'>icosa.gallery</a>.
|
||
You can add/create parcels in icosa.gallery, by publishing via <a href='https://openbrush.app'>OpenBrush</a>, <a href='https://openblocks.app'>OpenBlocks</a>, or <a href='https://blender.org' target='_blank'>Blender</a>.<br>
|
||
|
||
> 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 .. '<br><br><b>NOTE:</b> wget command not installed..plugin is disabled'
|
||
end
|
||
|
||
return plugin
|