Compare commits
9 Commits
v0.2.0
...
a2246015c9
| Author | SHA1 | Date | |
|---|---|---|---|
| a2246015c9 | |||
| 840b5e34df | |||
| 9857e77a1a | |||
| ea51793e2f | |||
| 5561038c25 | |||
| 95647069eb | |||
| 4c11d44918 | |||
| 996a62f55a | |||
| 99b84272c6 |
46
README.md
46
README.md
@@ -1,3 +1,47 @@
|
|||||||
# GlModelViewer
|
# GlModelViewer
|
||||||
|
|
||||||
MediaWiki extension for using model-viewer to display and annotate 3D models
|
MediaWiki extension for using model-viewer to display and annotate 3D models
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
Model-viewer configurations and annotations can be added as TOML in the file's description.
|
||||||
|
|
||||||
|
### Viewer Configuration
|
||||||
|
|
||||||
|
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.
|
||||||
|
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 can be configured using the `[annotations]` object.
|
||||||
|
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 (`[annotations."Label here"]`). Each object has two required properties and three optional properties.
|
||||||
|
|
||||||
|
| 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-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-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
|
||||||
|
|
||||||
|
Annotations sets are specific subsets of the listed annotations that can be shown on the model.
|
||||||
|
They are configured using the `[annotationSets]` object.
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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
5
composer.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"devium/toml": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"author": "Justin Georgi",
|
"author": "Justin Georgi",
|
||||||
"url": "https://gitea.azgeorgis.net/jgeorgi/mwModelViewer",
|
"url": "https://gitea.azgeorgis.net/jgeorgi/mwModelViewer",
|
||||||
"description": "This extension allows .glb and .gltf files to be added, displayed, and annotated in MediaWiki",
|
"description": "This extension allows .glb and .gltf files to be added, displayed, and annotated in MediaWiki",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"license-name": "MIT",
|
"license-name": "MIT",
|
||||||
"type": "media",
|
"type": "media",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
@@ -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"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -135,8 +137,9 @@ orb2degree = function(orbString, fix = null) {
|
|||||||
}
|
}
|
||||||
if (fix) {
|
if (fix) {
|
||||||
degArray = degArray.map((v, idx) => {
|
degArray = degArray.map((v, idx) => {
|
||||||
let fixReg = new RegExp('(\\d*.\\d{' + (((typeof fix) == 'object') ? fix[idx] : fix) + '})(\\d*)([a-z]*)')
|
//let fixReg = new RegExp('(-?\\d*\\.\\d{' + (((typeof fix) == 'object') ? fix[idx] : fix) + '})(\\d*)([a-z]*)')
|
||||||
return v.replace(fixReg,'$1$3')
|
//return v.replace(fixReg,'$1$3')
|
||||||
|
return Number.parseFloat(v).toFixed((typeof fix == 'object') ? fix[idx] : fix) + v.substring(v.search(/[a-z]/))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return degArray.join(' ')
|
return degArray.join(' ')
|
||||||
@@ -157,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
|
||||||
}
|
}
|
||||||
@@ -171,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)
|
||||||
}
|
}
|
||||||
@@ -185,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)
|
||||||
}
|
}
|
||||||
@@ -254,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,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
1150
modules/mini-st.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user