diff --git a/manyfold/root/hook.d/experience_updated/300-package_experience_zip.sh b/manyfold/root/hook.d/experience_updated/300-package_experience_zip.sh deleted file mode 100755 index bebdbe6..0000000 --- a/manyfold/root/hook.d/experience_updated/300-package_experience_zip.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -dir="$(dirname $1)" -cd "$dir" -echo "[package_experience.sh] zipping experience.zip" -zip -D ".xrforge/experience.zip * | tee -a .xrforge/log.txt diff --git a/manyfold/root/hook.d/experience_updated/300-package_godot_zip.sh b/manyfold/root/hook.d/experience_updated/300-package_godot_zip.sh old mode 100755 new mode 100644 diff --git a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb index fb8b3b2..064aa2f 100755 --- a/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb +++ b/manyfold/root/hook.d/experience_updated/300-package_janusxr.rb @@ -19,77 +19,89 @@ begin # Read and parse the JSON file data = JSON.parse( File.read( "datapackage.json" ) ) - logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) + if data['keywords'].empty? || data['keywords'].include?('janusxr') - XRForge.log("✅ starting build janusXR XR scene", logfile) + logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) - # Extract the desired field (assuming the field is named 'model_file') - thumb_file = data['image'] + XRForge.log("✅ starting build janusXR XR scene", logfile) - XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) - - # Get the base name of the thumbnail file without its extension - base_name = File.basename(thumb_file, File.extname(thumb_file)) - - model_file = nil # Initialize model_file to nil - - # Loop over the list of extensions - XRForge::MODEL_EXT.each do |ext| - # Construct the filename with the current extension - filename = "#{base_name}#{ext}" - - # Check if the file exists - if File.exist?(filename) - XRForge.log("✅ 3D file '#{filename}' detected", logfile) - model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename - break # Stop the loop once a file is found + # Extract the desired field (assuming the field is named 'model_file') + thumb_file = data['image'] + + XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) + + # Get the base name of the thumbnail file without its extension + base_name = File.basename(thumb_file, File.extname(thumb_file)) + + model_file = nil # Initialize model_file to nil + + # Loop over the list of extensions + XRForge::MODEL_EXT.each do |ext| + # Construct the filename with the current extension + filename = "#{base_name}#{ext}" + + # Check if the file exists + if File.exist?(filename) + XRForge.log("✅ 3D file '#{filename}' detected", logfile) + model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename + break # Stop the loop once a file is found + else + # Log a message for the file that was not found, but don't stop + XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile) + end + end + + # Check if a model file was found after the loop + if model_file + XRForge.log("✅ Final model file: '#{model_file}'", logfile) else - # Log a message for the file that was not found, but don't stop - XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile) + XRForge.log("❌ No suitable 3D file found for XR Fragments- / JanusXR-compatible experience", logfile) + end + + # Get the value of the environment variable FEDERATE_DRIVE_HOST + federate_drive_host = ENV['FEDERATE_DRIVE_HOST'] + + # Define the HTML content using a multi-line string (heredoc) + # Ruby's heredoc allows for variable interpolation (using #{}) + jml = <<~JML + + + + + + + + + JML + + html = <<~HTML + + + + janusxr room + + + + + #{jml} + + + + HTML + + File.write('.xrforge/janusxr.html', html) + File.write('.xrforge/scene.jml', jml) + + XRForge.log("✅ generated scene.jml", logfile) + XRForge.log("✅ generated janusxr.html", logfile) + XRForge.log(" ", logfile) + + # tag it! + if ! data['keywords'].include?('janusxr') + data['keywords'].push('janusxr') + File.write(file_path, JSON.pretty_generate(data) ) end end - - # Check if a model file was found after the loop - if model_file - XRForge.log("✅ Final model file: '#{model_file}'", logfile) - else - XRForge.log("❌ No suitable 3D file found for XR Fragments- / JanusXR-compatible experience", logfile) - end - - # Get the value of the environment variable FEDERATE_DRIVE_HOST - federate_drive_host = ENV['FEDERATE_DRIVE_HOST'] - - # Define the HTML content using a multi-line string (heredoc) - # Ruby's heredoc allows for variable interpolation (using #{}) - jml = <<~HTML - - - - janusxr room - - - - - - - - - - - - - - - - HTML - - # Write the content to the specified file - # File.write is the concise equivalent of 'echo "$jml" > filename' - File.write('.xrforge/janusxr.html', jml) - - XRForge.log("✅ written janusxr.html", logfile) - - XRForge.log(" ", logfile) rescue Errno::ENOENT puts "File #{filename} not found" diff --git a/manyfold/root/hook.d/experience_updated/300-package_mml.rb b/manyfold/root/hook.d/experience_updated/300-package_mml.rb new file mode 100755 index 0000000..f31efff --- /dev/null +++ b/manyfold/root/hook.d/experience_updated/300-package_mml.rb @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +require 'json' +require_relative './../../xrforge.rb' + +# Check if a filename is provided +if ARGV.length != 1 + puts "Usage: #{$0} " + exit 1 +end + +filename = ARGV[0] + +begin + + # Change the directory + dir = File.dirname(filename) + Dir.chdir( File.dirname(filename) ) + # Read and parse the JSON file + data = JSON.parse( File.read( "datapackage.json" ) ) + + if data['keywords'].empty? || data['keywords'].include?('mml') + + logfile = File.join( File.dirname(filename), ".xrforge/log.txt" ) + + XRForge.log("✅ starting build mml XR scene", logfile) + + # Extract the desired field (assuming the field is named 'model_file') + thumb_file = data['image'] + + XRForge.log("✅ thumbnail sidecar-file '#{thumb_file}' detected", logfile) + + # Get the base name of the thumbnail file without its extension + base_name = File.basename(thumb_file, File.extname(thumb_file)) + + model_file = nil # Initialize model_file to nil + + # Loop over the list of extensions + XRForge::MODEL_EXT.each do |ext| + # Construct the filename with the current extension + filename = "#{base_name}#{ext}" + + # Check if the file exists + if File.exist?(filename) + XRForge.log("✅ 3D file '#{filename}' detected", logfile) + model_file = "#{dir.gsub("/mnt/","")}/#{filename}" # Store the found filename + break # Stop the loop once a file is found + else + # Log a message for the file that was not found, but don't stop + XRForge.log("⚠️ 3D file '#{filename}' not detected", logfile) + end + end + + # Check if a model file was found after the loop + if model_file + XRForge.log("✅ Final model file: '#{model_file}'", logfile) + else + XRForge.log("❌ No suitable 3D file found for XR Fragments- / MML-compatible experience", logfile) + end + + # Get the value of the environment variable FEDERATE_DRIVE_HOST + federate_drive_host = ENV['FEDERATE_DRIVE_HOST'] + + # https://viewer.mml.io/main/v1/?url=https%3A%2F%2Ffoo.org%2Fbar.mml + mml = <<~MML + + MML + + File.write('.xrforge/scene.mml', mml) + + XRForge.log("✅ generated scene.mml", logfile) + XRForge.log(" ", logfile) + + # tag it! + if ! data['keywords'].include?('mml') + data['keywords'].push('mml') + File.write(file_path, JSON.pretty_generate(data) ) + end + end + +rescue Errno::ENOENT + puts "File #{filename} not found" +rescue JSON::ParserError + puts "Error parsing JSON from #{filename}" +rescue => e + puts "An error occurred: #{e.message}" +end diff --git a/manyfold/usr/src/app/app/views/models/_tags_info.html.erb b/manyfold/usr/src/app/app/views/models/_tags_info.html.erb new file mode 100644 index 0000000..93ad7d2 --- /dev/null +++ b/manyfold/usr/src/app/app/views/models/_tags_info.html.erb @@ -0,0 +1,32 @@ + Features can be toggled via tags:
+
+ + + + + + + + + + + + + +
+ menu + + This will generate a navigator-menu into your main 3D file.
+ The links can be edited <%= link_to "here", edit_model_path(@model) %> +
+ janusxr + + This generates an JanusXR address.
+ JanusXR is an Open Immersive Web-layer since 2015, which allows existing webpages to become spatial.
+ It is Free and Opensource, and allows you to meet others in this experience (avatars, chat and voice etc).
+
+ mml + + This generates an MML address.
+ Metaverse Markup Language (MML) is an open markup language used to define experiences. +
diff --git a/manyfold/usr/src/app/app/views/models/show.html.erb b/manyfold/usr/src/app/app/views/models/show.html.erb index 5818f2e..9f0c960 100644 --- a/manyfold/usr/src/app/app/views/models/show.html.erb +++ b/manyfold/usr/src/app/app/views/models/show.html.erb @@ -1,298 +1,338 @@ -<% content_for :head do %> - <%= tag.meta property: "og:type", content: "website" %> - <%= tag.meta property: "og:image", content: model_model_file_url(@model, @model.preview_file, format: @model.preview_file.extension) if @model.preview_file&.is_image? && !@model.sensitive %> - <%= tag.meta name: "description", content: truncate(sanitize(@model.notes), length: 80) if @model.notes.present? %> - <%= tag.meta name: "fediverse:creator", content: @model.creator.federails_actor.at_address if @model.creator %> - <%= tag.link rel: "alternate", type: Mime[:oembed], href: model_url(@model, format: :oembed), title: @model.name %> -<% end %> - -
- <%= turbo_stream_from @model %> -

- - <%= @model.name %> - - <%= link_to icon("search", t(".search")), - "https://yeggi.com/q/#{ERB::Util.url_encode(@model.name)}/", - class: "btn btn-outline", target: "manyfold_search", rel: "noreferrer" %> -

- -
-
- <% if @locked_files > 0 %> -
<%= t(".preview", count: @locked_files) %>
- <% end %> - - <%= render "image_carousel", images: @images %> - - <%= card(:secondary) do %> -

<%= markdownify @model.notes %>

- <% end if @model.notes.present? %> - - <% if @num_files > 0 %> -

<%= t(".files") %>

-
- <%= render partial: "file", collection: @groups.delete(nil) %> -
- <% @groups.each_pair do |group, files| %> -

<%= group.strip.careful_titleize %>*

-
- <%= render partial: "file", collection: files %> -
- <% end %> - <% end %> -
- -
-
+ <% if @model.creator %> + + <%= icon "person", Creator.model_name.human %> + <%= link_to @model.creator.name, @model.creator, itemprop: "author" %> + + <% end %> + <% if ENV['FEDERATE_DRIVE_HOST'].present? %> + + <% if @model.tags.where(name: "janusxr" ).any? %> + + + + + + <% + fediURL = ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/janusxr.html" + %> + <%= link_to "JanusXR address", fediURL, target:"_blank" %> + + +
+ +
+   + + This is the JanusXR address.
+ JanusXR is an Open Immersive Web-layer since 2015, which allows existing webpages to become spatial.
+ It is Free and Opensource, and allows you to meet others in this experience (avatars, chat and voice etc).
+
+
+
+ + + + <% + # lets generate a JanusXR spatial layer for the current scene + jmlfile = @model.library.path+"/"+@model.path+"/.xrforge/scene.jml" + jml = "" + if File.exist?(jmlfile) + jml = File.read(jmlfile) + end + %> + + + + <% end %> + + <% if @model.tags.where(name: "mml" ).any? %> + + + + + + <%= link_to "MML address", "https://viewer.mml.io/main/v1/?url="+URI.encode_www_form_component(ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/scene.mml"), target:"_blank" %> + " target="_blank"> + +
+ +
+   + + This is the MML address.
+ Metaverse Markup Language (MML) is an open markup language used to define experiences. +
+
+
+ + + <% end %> + + + + + + + <%= link_to "build log", ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/log.txt", target: "_blank" %> + +
+ +
+   + + This is the build log of XR Forge.
+ When you add files, they are processed, validated (for XR Fragment compliance).
+ <% render 'models/tags_info' %> +
+
+
+ + + + + + + + <%= link_to "Godot project", ENV['FEDERATE_DRIVE_HOST']+"/"+@model.library.name+"/"+@model.path.gsub("#","%23")+"/.xrforge/godot.zip" %> + +
+ +
+   + + This is a Godot project which wraps your (3D file) experience.
+ Godot is a Free and Opensource Game engine.
+ The Godot project is basically its own XR Fragment browser (which you can extend).

+ WARNING: use progressive enhancement so your 3D file experience will always run in other XR Fragment viewers. +
+
+
+ + + <% end %> + + <% if @model.collection %> + + <%= icon "collection", Collection.model_name.human(count: 100) %> + <%= link_to @model.collection.name, models_path({collection: @model.collection}.merge(@filter&.to_params)) %> + + <% end %> + <% if SiteSettings.show_libraries || current_user&.is_administrator? %> + + <%= icon "boxes", Library.model_name.human %> + <%= link_to @model.library.name, models_path({library: @model.library}.merge(@filter&.to_params)) %> + + <% end %> + <% if @model.license %> + + <%= icon "card-heading", t(".license") %> + + <%= Spdx.licenses[@model.license]&.fetch("reference") ? + link_to(t_license(@model.license), Spdx.licenses[@model.license]["reference"], itemprop: "license") : + t_license(@model.license) %> + + + <% end %> + <% if @model.sensitive %> + + <%= icon("explicit", Model.human_attribute_name(:sensitive)) %> + + <%= Model.human_attribute_name(:sensitive) %> + + + <% end %> + + <%= icon "folder", t(".path") %> + + <%= content_tag(:code, class: "path") { safe_join @model.path.split("/"), safe_join([tag.wbr, "/"]) } %> + <% unless @model.contains_other_models? %> +
<%= button_tag(t(".organize.button_text"), class: "btn btn-warning btn-sm", "data-bs-toggle": "modal", "data-bs-target": "#confirm-move") if @model.needs_organizing? && policy(@model).edit? %>
+ <% end %> + + + + <%= icon "tag", t(".tags") %> + <%= render "tag_list", tags: @model.tags.order(taggings_count: :desc, name: :asc), filter: @filter %> + +
+ +
+   + + <% render 'models/tags_info' %> + +
+
+ + + <% if SiteSettings.social_enabled? %> + + <%= icon "people", t(".followers") %> + <%= t("general.followers", count: @model.followers.count) %> + + <% end %> + + <%= render Components::AccessIndicator.new(object: @model, text: false) %> + <%= render Components::AccessIndicator.new(object: @model, text: true, icon: false) %> + + + <%= render Components::FollowButton.new(follower: current_user, target: @model) unless @model.remote? %> + <%= render Components::GoButton.new(icon: "pencil", label: t("general.edit"), href: edit_model_path(@model), variant: "primary") if policy(@model).edit? %> + <%= render Components::DoButton.new(icon: "trash", label: t("general.delete"), href: model_path(@model), method: :delete, variant: "outline-danger", confirm: translate("models.destroy.confirm")) if policy(@model).destroy? %> + <% end %> + +
+ <%= render Components::DownloadButton.new(model: @model) %> +
+ + <%= card :secondary, t("layouts.card_list_page.actions_heading") do %> + <%= render Components::ReportButton.new(object: @model, path: new_model_report_path(@model)) %> + <% end if SiteSettings.multiuser_enabled? %> + + <% if !@model.parents.empty? && policy(@model).merge? %> + <%= card :danger, t(".merge.heading") do %> +

+ <%= t(".merge.warning") %> +

+ <%= t(".merge.with") %>: + <% @model.parents.each do |parent| %> + <%= render Components::DoButton.new(icon: "union", label: parent.name, href: merge_models_path(target: parent.public_id, models: [@model.public_id]), method: :post, variant: "danger") %> + <% end %> + <% end %> + <% end %> + + <%= render partial: "problem", collection: @model.problems.visible(problem_settings) %> + + <%= card :secondary, t(".files_card.heading") do %> + Top + + <%= link_to t(".files_card.bulk_edit"), bulk_edit_model_model_files_path(@model), class: "btn btn-secondary" if policy(@model).edit? %> + <%= link_to t(".rescan"), scan_model_path(@model), class: "btn btn-warning", method: :post if policy(@model).scan? %> + <% end %> + + <%= render "links_card", links: @model.links %> + + + + <% if policy(@model).upload? %> + <%= card :warning, t(".upload_card.heading") do %> + <%= form_with url: model_model_files_path(@model), id: "upload-form", class: "d-grid" do |form| %> + + <%= content_tag :div, nil, class: "mb-3", data: { + controller: "upload", + action: "turbo:morph@window->upload#reconnect", + max_file_size: SiteSettings.max_file_upload_size, + allowed_file_types: input_accept_string, + upload_endpoint: upload_path + } %> + <%= submit_tag translate(".submit"), class: "btn btn-primary d-block" %> + <% end %> + <% end %> + <% end %> + + + diff --git a/nix/docker.nix b/nix/docker.nix index 83ce7d2..72148e3 100644 --- a/nix/docker.nix +++ b/nix/docker.nix @@ -76,6 +76,7 @@ rec pkgs.pkgsStatic.inotify-tools # inotifywait e.g. pkgs.pkgsStatic.zip # inotifywait e.g. pkgs.pkgsStatic.ts # job management + pkgs.janus-gateway # webrtc server myAssimp # cli 3D editing/conversion ./.. ];