Merge pull request 'Convert configs from json to toml' (#58) from toml_annot into main

Reviewed-on: #58
This commit is contained in:
2025-05-02 03:54:19 +00:00
8 changed files with 1203 additions and 33 deletions

View File

@@ -4,30 +4,44 @@ MediaWiki extension for using model-viewer to display and annotate 3D models
## Metadata ## Metadata
Model-viewer configurations and annotations can be added as a json string in the file's description. Model-viewer configurations and annotations can be added as TOML in the file's description.
### Viewer Configuration ### Viewer Configuration
Configuration of the model-viewer instance is contained in the `viewerConfig` object. Configuration of the model-viewer instance is contained in the `[viewerConfig]` object.
This object can contain any number of child objects each of which describes a different possible configuration. This object can contain any number of child objects each of which describes a different possible configuration.
Each configuration can have properties matching the any of the attributes of the model-viewer element except for `src`, `class`, `interpolation-delay`, and `interaction-prompt`. Each configuration can have properties matching the any of the attributes of the model-viewer element except for `src`, `class`, `interpolation-delay`, and `interaction-prompt`.
If adding a poster image, the value of the `poster` property must be the name of an image in the Wiki's File namespace (without the namespace header, e.g., `"My poster.jpg"`).
### Annotations ### Annotations
Annotations can be configured using the `annotations` object. Annotations can be configured using the `[annotations]` object.
This object can contain any number of child objects each of which describes a different possible annotation. This object can contain any number of child objects each of which describes a different possible annotation.
The label for each annotation is the name of the child object. Each object has two required properties and two optional properties. The label for each annotation is the name of the child object (`[annotations."Label here"]`). Each object has two required properties and three optional properties.
| Property | Required | Description | Example | | Property | Required | Description | Example |
|----------|----------|-------------|---------| |----------|----------|-------------|---------|
| `data-position` | Yes | String containing the X, Y, and Z coordinates of the annotation. | "0.02297m -0.02544m 0.01780m" | | **data-position** | Yes | String containing the X, Y, and Z coordinates of the annotation. | "0.02297m -0.02544m 0.01780m" |
| `data-normal` | Yes | String containing the X, Y, and Z coordinates of the normal vector of the annotation. | "0.78966m -0.53116m 0.30709m" | | **data-normal** | Yes | String containing the X, Y, and Z coordinates of the normal vector of the annotation. | "0.78966m -0.53116m 0.30709m" |
| `data-orbit` | No | String containing the orbit vector of the camera to view annotation. | "2.31rad 2.01rad 0.65m" | | **data-orbit** | No | String containing the orbit vector of the camera to view annotation. | "2.31rad 2.01rad 0.65m" |
| `data-target` | No | String containing the X, Y, and Z coordinates of the camera target to view annotation. | "-0.01029m 0.00071m 0.01138m" | | **data-target** | No | String containing the X, Y, and Z coordinates of the camera target to view annotation. | "-0.01029m 0.00071m 0.01138m" |
| **field-of-view** | No | String containing the angle of the field of view in degrees. | "35.25deg" |
### Annotation sets ### Annotation sets
Annotations sets are specific subsets of the listed annotations that can be shown on the model. Annotations sets are specific subsets of the listed annotations that can be shown on the model.
They are configured using the `annotationSets` object. They are configured using the `[annotationSets]` object.
This object can contain any number of child objects each of which contains an array of strings matching the names of the annotation to be included. This object can contain any number properties each of which contains an array of strings matching the names of the annotation to be included.
The property name is used as the annotation set identifier.
By default, all annotations are rendered on a model. By default, all annotations are rendered on a model.
## Linking
When adding the model file to a page, use the standard `[[File:Model name.glb]]` internal file link.
The link supports three optional properties.
| Property | Description | Default |
|----------|-------------|---------|
| **view** | Use one of the non-default configurations specified in the `[viewerConfig]` object. | default viewer configuration |
| **set** | Display only the annotations listed in the specified annotation set from the `[annotationSets]` object. | show all annotations |
| **transform** | Reorder the displayed annotations. Can be either *rand* or *alpha*. | annotations are numbered in the order they are listed in the `[annotations]` object |

5
composer.json Normal file
View File

@@ -0,0 +1,5 @@
{
"require": {
"devium/toml": "*"
}
}

View File

@@ -74,6 +74,7 @@
], ],
"packageFiles": [ "packageFiles": [
"glmv-prev.js", "glmv-prev.js",
"mini-st.js",
"glmv-mvconfig.js", "glmv-mvconfig.js",
"glmv-hs.js" "glmv-hs.js"
] ]

View File

@@ -44,7 +44,7 @@ class GlModelHooks {
} }
/** /**
* Render the config json in a <pre> tag * Render the config toml in a <pre> tag
* *
* @param $input The text inside the custom tag * @param $input The text inside the custom tag
* @param array $args Any attributes given in the tag * @param array $args Any attributes given in the tag
@@ -170,15 +170,8 @@ class GlModelHooks {
$uploadFormObj->mComment .= <<<CONF $uploadFormObj->mComment .= <<<CONF
<mvconfig> <mvconfig>
{ [viewerConfig.default]
"viewerConfig": { camera-controls = true
"default": {
"camera-controls": true
}
},
"annotations": {},
"annotationSets": {}
}
</mvconfig> </mvconfig>
CONF; CONF;
} }

View File

@@ -1,6 +1,9 @@
<?php <?php
namespace MediaWiki\Extension\GlModelViewer; namespace MediaWiki\Extension\GlModelViewer;
require_once __DIR__ . '/../vendor/autoload.php';
use Devium\Toml\Toml;
use MediaWiki\MediaWikiServices; use MediaWiki\MediaWikiServices;
use MediaTransformOutput; use MediaTransformOutput;
use ConfigFactory; use ConfigFactory;
@@ -49,7 +52,7 @@ class GlModelTransformOutput extends MediaTransformOutput {
public function toHtml($options = []) { public function toHtml($options = []) {
$descriptText = $this->file->getDescriptionText(); $descriptText = $this->file->getDescriptionText();
preg_match('/<pre mvconfig.*?>([\S\s]*?)<\/pre>/',$descriptText,$modelDescript); preg_match('/<pre mvconfig.*?>([\S\s]*?)<\/pre>/',$descriptText,$modelDescript);
$metadata = json_decode($modelDescript[1], true); $metadata = toml_decode($modelDescript[1], true);
if ($this->thumb) { if ($this->thumb) {
$poster = $metadata['viewerConfig'][$this->view]['poster'] ?? false; $poster = $metadata['viewerConfig'][$this->view]['poster'] ?? false;

View File

@@ -1,3 +1,5 @@
const TOML = require('./mini-st.js')
let deleteHotspot = null let deleteHotspot = null
let grabHotspot = null let grabHotspot = null
@@ -34,7 +36,7 @@ clickAddHotspot = function(e) {
hsOutput['data-target'] = `${targetObj.x.toFixed(5)}m ${targetObj.y.toFixed(5)}m ${targetObj.z.toFixed(5)}m` hsOutput['data-target'] = `${targetObj.x.toFixed(5)}m ${targetObj.y.toFixed(5)}m ${targetObj.z.toFixed(5)}m`
hsOutput['field-of-view'] = Number.parseFloat(targetModel.getFieldOfView()).toFixed(5) + 'deg' hsOutput['field-of-view'] = Number.parseFloat(targetModel.getFieldOfView()).toFixed(5) + 'deg'
mvconfig.annotations['Hotspot ' + (Object.keys(mvconfig.annotations).length + 1)] = hsOutput mvconfig.annotations['Hotspot ' + (Object.keys(mvconfig.annotations).length + 1)] = hsOutput
let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`) let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
$('#wpTextbox1').val(newText) $('#wpTextbox1').val(newText)
} }
readMvconfig() readMvconfig()
@@ -147,7 +149,7 @@ releaseAnnotation = function(e) {
"data-target": newTarg, "data-target": newTarg,
"field-of-view": newFov "field-of-view": newFov
} }
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`) const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
$('#wpTextbox1').val(newText) $('#wpTextbox1').val(newText)
} }
grabHotspot = null grabHotspot = null

View File

@@ -1,3 +1,5 @@
const TOML = require('./mini-st.js')
/** /**
* Convert text in the preview text editor to js object * Convert text in the preview text editor to js object
* *
@@ -6,7 +8,7 @@
extractMvconfig = function() { extractMvconfig = function() {
const editText = $('#wpTextbox1').val() const editText = $('#wpTextbox1').val()
const extractConfig = editText.match(/<mvconfig>([\S\s]*?)<\/mvconfig>/) const extractConfig = editText.match(/<mvconfig>([\S\s]*?)<\/mvconfig>/)
let mvconfig = (extractConfig.length >= 2) ? JSON.parse(extractConfig[1]) : {viewerConfig: {}, annotations: {}, annotationSets: {}} let mvconfig = (extractConfig.length >= 2) ? TOML.parse(extractConfig[1]) : {viewerConfig: {}, annotations: {}, annotationSets: {}}
if (mvconfig.viewerConfig === undefined) { if (mvconfig.viewerConfig === undefined) {
mvconfig.viewerConfig = { mvconfig.viewerConfig = {
default: { default: {
@@ -24,7 +26,7 @@ extractMvconfig = function() {
} }
/** /**
* Reads the json string in the edit panel * Reads the TOML string in the edit panel
* and updates hotspot elements and menu settings * and updates hotspot elements and menu settings
* *
* @return {bool|object} arrays of view and set names on successful read and update false on failure * @return {bool|object} arrays of view and set names on successful read and update false on failure
@@ -90,8 +92,8 @@ readMvconfig = function() {
} }
/** /**
* Parses the current hotspots into json object * Parses the current hotspots into TOML object
* and writes the json string to the edit panel * and writes the TOML string to the edit panel
* *
* @return {bool} true on successful write to edit panel * @return {bool} true on successful write to edit panel
*/ */
@@ -109,7 +111,7 @@ writeMvconfig = function () {
if (Object.keys(annotationsObj).length === 0) return false if (Object.keys(annotationsObj).length === 0) return false
const [currentText, mvconfig] = extractMvconfig() const [currentText, mvconfig] = extractMvconfig()
mvconfig.annotations = annotationsObj mvconfig.annotations = annotationsObj
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`) const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
$('#wpTextbox1').val(newText) $('#wpTextbox1').val(newText)
return true return true
} }
@@ -158,7 +160,7 @@ toggleCameraControl = function(view) {
} else { } else {
delete mvconfig.viewerConfig[currentView]['camera-controls'] delete mvconfig.viewerConfig[currentView]['camera-controls']
} }
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate) $('#wpTextbox1').val(textUpdate)
return newControl return newControl
} }
@@ -172,7 +174,7 @@ addAnnotationSet = function(newSet) {
const mView = $('model-viewer')[0] const mView = $('model-viewer')[0]
let [currentText, mvconfig] = extractMvconfig() let [currentText, mvconfig] = extractMvconfig()
mvconfig.annotationSets[newSet] = [] mvconfig.annotationSets[newSet] = []
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate) $('#wpTextbox1').val(textUpdate)
selectAnnotationSet(newSet) selectAnnotationSet(newSet)
} }
@@ -186,7 +188,7 @@ addViewConfig = function(newView) {
const mView = $('model-viewer')[0] const mView = $('model-viewer')[0]
let [currentText, mvconfig] = extractMvconfig() let [currentText, mvconfig] = extractMvconfig()
mvconfig.viewerConfig[newView] = { "camera-controls": true } mvconfig.viewerConfig[newView] = { "camera-controls": true }
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate) $('#wpTextbox1').val(textUpdate)
selectViewConfig(newView) selectViewConfig(newView)
} }
@@ -255,7 +257,7 @@ writeCameraOrbit = function() {
mvconfig.viewerConfig.default['camera-orbit'] = newOrbit mvconfig.viewerConfig.default['camera-orbit'] = newOrbit
mvconfig.viewerConfig.default['camera-target'] = newTarget mvconfig.viewerConfig.default['camera-target'] = newTarget
mvconfig.viewerConfig.default['field-of-view'] = newField mvconfig.viewerConfig.default['field-of-view'] = newField
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate) $('#wpTextbox1').val(textUpdate)
} }
@@ -277,6 +279,6 @@ writeCameraLimit = function(axis, limit) {
oldOrbitVals[valueIndex] = newOrbitVals[valueIndex] oldOrbitVals[valueIndex] = newOrbitVals[valueIndex]
mvconfig.viewerConfig.default[`${limit}-camera-orbit`] = oldOrbitVals.join(' ') mvconfig.viewerConfig.default[`${limit}-camera-orbit`] = oldOrbitVals.join(' ')
mView.setAttribute(`${limit}-camera-orbit`, oldOrbitVals.join(' ')) mView.setAttribute(`${limit}-camera-orbit`, oldOrbitVals.join(' '))
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate) $('#wpTextbox1').val(textUpdate)
} }

1150
modules/mini-st.js Normal file

File diff suppressed because it is too large Load Diff