xrforge-v2/src/plugin/openbrush.lua

365 lines
14 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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