Browse Source

Merge branch 'master' into 278-raceway

rickard 3 years ago
parent
commit
ead0e50679
72 changed files with 1569 additions and 773 deletions
  1. 41 0
      .github/workflows/release.yml
  2. 0 114
      .travis.yml
  3. 4 5
      BasicGameTemplate/nbproject/project.properties
  4. 3 4
      JME3TestsTemplate/nbproject/project.properties
  5. BIN
      branding/core/core.jar/org/netbeans/core/startup/about.png
  6. BIN
      branding/core/core.jar/org/netbeans/core/startup/splash.gif
  7. 1 1
      branding/modules/org-netbeans-core.jar/org/netbeans/core/ui/Bundle.properties
  8. 23 26
      build.gradle
  9. 2 2
      gradle.properties
  10. BIN
      gradle/wrapper/gradle-wrapper.jar
  11. 1 1
      gradle/wrapper/gradle-wrapper.properties
  12. 153 107
      gradlew
  13. 7 18
      gradlew.bat
  14. BIN
      harness-override/app.exe
  15. BIN
      harness-override/app64.exe
  16. 5 5
      harness-override/override.properties
  17. 2 7
      jdks/download-jdks.sh
  18. 173 72
      jme3-core/src/com/jme3/gde/core/assets/ExternalChangeScanner.java
  19. 8 1
      jme3-core/src/com/jme3/gde/core/editor/nodes/Diagram.java
  20. 2 2
      jme3-core/src/com/jme3/gde/core/editor/nodes/NodePanel.java
  21. 28 0
      jme3-core/src/com/jme3/gde/core/scene/controller/SceneToolController.java
  22. 3 1
      jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeMesh.java
  23. 7 3
      jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeSpatialChildren.java
  24. 42 285
      jme3-core/src/com/jme3/gde/core/util/SpatialUtil.java
  25. 64 0
      jme3-core/src/com/jme3/gde/core/util/TaggedSpatialFinder.java
  26. 121 0
      jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyAnimationDataFromOriginal.java
  27. 42 0
      jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyMaterialDataFromOriginal.java
  28. 95 0
      jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyMeshDataFromOriginal.java
  29. 54 0
      jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyTransformDataFromOriginal.java
  30. 19 0
      jme3-core/src/com/jme3/gde/core/util/datatransfer/SpatialDataTransferInterface.java
  31. 4 10
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java
  32. 9 0
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/BackdropPanel.java
  33. 3 0
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/Bundle.properties
  34. 39 1
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.form
  35. 37 2
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java
  36. 7 10
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorlElement.java
  37. 57 4
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/ShaderNodeDiagram.java
  38. 3 3
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/ShaderNodePanel.java
  39. 36 0
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/DefinesBlock.java
  40. 0 3
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/InputMappingsBlock.java
  41. 4 15
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/ShaderNodeBlock.java
  42. 80 13
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/ShaderNodesBlock.java
  43. 4 2
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java
  44. 2 1
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/UberStatement.java
  45. 76 0
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/leaves/DefineBlock.java
  46. 5 4
      jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/leaves/LeafStatement.java
  47. 26 5
      jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java
  48. 29 3
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/SNDefDataObject.java
  49. 29 3
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/SNDefVisualElement.java
  50. 29 3
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefVisualPanel1.java
  51. 29 3
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefVisualPanel2.java
  52. 29 3
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java
  53. 41 4
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardPanel1.java
  54. 29 3
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardPanel2.java
  55. 0 5
      jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/sNDef.html
  56. 2 0
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties
  57. 23 3
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form
  58. 32 8
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java
  59. BIN
      jme3-templates/src/com/jme3/gde/templates/GradleDesktopGameProject.zip
  60. BIN
      nbi/antlib/nbi-ant-tasks.jar
  61. BIN
      nbi/antlib/nbi-engine.jar
  62. BIN
      nbi/antlib/nbi-registries-management.jar
  63. 1 1
      nbi/antlib/version
  64. 0 3
      nbproject/platform.properties
  65. 2 2
      nbproject/project.properties
  66. BIN
      resources/splashscreen/splash.gif
  67. BIN
      resources/splashscreen/splash.psd
  68. BIN
      resources/splashscreen/splash2.psd
  69. BIN
      resources/splashscreen/splash2_jme32.psd
  70. BIN
      resources/splashscreen/splash_jme32.psd
  71. BIN
      resources/splashscreen/updatersplash.gif
  72. 2 2
      version.gradle

+ 41 - 0
.github/workflows/release.yml

@@ -0,0 +1,41 @@
+# This Action will build the SDK and if this succeeds, create a github release
+name: Release Builds
+on:
+  push:
+    tags:
+      - "*"
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up JDK 11
+      uses: actions/setup-java@v3
+      with:
+        distribution: temurin
+        java-version: 11
+    - name: Grant execute permission for gradle
+      run: chmod +x gradlew
+    - name: Build the SDK
+      run: ./gradlew buildSdk -Ptag_name=${{ github.ref_name }}
+    - name: Build the JDKs
+      run: bash download-jdks.sh
+      working-directory: jdks
+    - name: Override Harness (custom icon)
+      run: ./gradlew overrideHarness -Ptag_name=${{ github.ref_name }}
+    - name: Build Installers
+      run: ant -Dstorepass="$NBM_SIGN_PASS" -Dpack200.enabled=true set-spec-version build-installers unset-spec-version
+      env:
+        BUILD_X86: true
+        BUILD_X64: true
+        BUILD_OTHER: true
+    - name: Fix Platform Independent Build
+      run: ./gradlew fixPlatformIndependent -Ptag_name=${{ github.ref_name }}
+    - name: Create Release
+      uses: softprops/action-gh-release@v1
+      with:
+        files: dist/jmonkeyplatform*.*
+        tag_name:  ${{ github.ref }}
+        name: Release ${{ github.ref }}
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 0 - 114
.travis.yml

@@ -1,114 +0,0 @@
-language: java
-jdk: openjdk11
-dist: bionic
-
-before_cache:
- - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
-cache:
-  directories:
-   - $HOME/.gradle/caches/
-   - $HOME/.gradle/wrapper/
-   - jdks/local/*/compiled
-
-addons:
-  #ssh_known_hosts: updates.jmonkeyengine.org
-  apt:
-    packages:
-    - p7zip-full
-    - cabextract
-    - maven
-    - patch
-
-# branches:
-#   only:
-#   - master
-
-env:
-  - BUILD_X64=true
-  - BUILD_X86=true
-  - BUILD_OTHER=true
-
-notifications:
-  slack:
-    on_success: change
-    on_failure: always
-    rooms:
-      secure: cOYkCWyBNtM4QiqNbGvhCE2lFxHSnmLfRl1wLJzeDBYEDbg1nOXayRwyH1dQzWCvhDhqv2qWHHASgddE14JwnVB7p56DcaQWAJ5yn/OyY9GzaHuz59Xm6LbFbz5JfabHY7LczuBlRtISlyfz4eHD5BOvfCzx7D3GI44kQX99BCFr1mqDjQEUyTPwhLolFPL5+zx7J669ud2Ba0TywtaWsXeOUvORAXAdlQv1RRAmQvUK9DIYyq0Z3fzr/uXEBaPAz50JFXkMs00Z8Dutdiu9jd/SsRnEv0O+ns75outu6WK2UwS1xHhcdW7bkMuTmRpGoec7XbbjSaz6oYsHSp8kyuPhLEzS2ba2QIxDmOkF/erejeAMdDlsIKwRMpizRCh/8gMZR2nNEzdHQ0gbgEk83PFYgQw+amtlOk61l6THopwLtVpDiiE1Elz5ev7KqSr//qWQgoHBFabQOgE5KjfxmLDmuUNWaZyuJi6JTwsxB04NGAa3zpQ6RKQ7dgGBZF7QIQ+f648oxVlLIK+T3VAdK47s94XKTRJ0CqRoA6nI2MCaLbU9zvS8uAWoLOIKw6ec2qexPaVCE+TO0780+x6tBFSYexwH8fwmEl1nPqBldipYCLIRZV7XbJh3bhfPksXuz2B6poJ/wL5gLtWNAXnBoWyI7Vu/PHbFsn+VhLUkYSU=
-
-install:
-  - chmod u+x build_engine.sh fix_engine.sh
-  - ./build_engine.sh
-  # Now we have to patch the engine
-  - ./fix_engine.sh
-  # Can't do that currently was we now use engine/ as subproject...
-  # Remove Engine Source and built class files since travis is low on disk space
-  # - rm -rf engine/
-  - ./gradlew buildSdk
-  #- '[ -z "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ant -Dstorepass="$NBM_SIGN_PASS" hudson-nightly | awk "{printf(\".\"); fflush(stdout)}" || :'
-  #- '[ -z "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && scp -rp -i nbproject/private/www-updater.key build/updates/* [email protected]:/var/www/updates/nightly/3.1/plugins || :'
-  
-#script:
-#  - ./gradlew check
-#  - ./gradlew createZipDistribution
-
-before_deploy:
-  # In case of STABLE (see hudson-stable target)
-  # the above gradlew already sets the correct ant properties to build
-  # Include suite.nbms again, when we upload nbms again.
-  
-  # Build the JDKs we package
-  - cd jdks
-  - ./download-jdks.sh
-  # Reduce Diskspace by 3G
-  - rm -rf jdks/local/*/{downloads,linux-i586,linux-x64,windows-i586,windows-x64}
-  - cd ../
-  
-  # Force Freeing Space.
-  - rm -rf build/
-  - ./gradlew overrideHarness # This will patch the launcher exes to have our custom icon
-  - set -o pipefail # Allow failing in ant to be up-propagated
-  - ant -Dstorepass="$NBM_SIGN_PASS" -Dpack200.enabled=true set-spec-version build-installers unset-spec-version | awk '{printf("."); fflush(stdout)}'
-  
-  # Has to be done before deploy (due to the rm command)
-  #- scp -rp -i nbproject/private/www-updater.key build/updates/* [email protected]:/var/www/updates/stable/3.1/plugins
-  - '[ "$BUILD_OTHER" != "true" ] && rm -rf dist/jmonkeyplatform.zip || :'
-  - '[ "$BUILD_OTHER" == "true" ] && ./gradlew fixPlatformIndependent || :'
-  - rm -rf build/
-
-deploy:
-  provider: releases
-  api_key:
-    secure: IoGNk8dOmBIB/lXqgd7we7UjyFULww6y43F/Dwx2KZZ6NqC4tvDfBpqEyYhhLC0Ltgpm6vO/+0EepnqmBO5CkDYBECcwpIQX+ndgIOZQLMonXQ3glcjM5yoNtoUbFEn7xvI2ntAKbUkKmWnjNVquHF7t7jT8QfUPymUdV+f1eHapQFtcx2VuDoGF4/1fZuygS5pNPgkw7bduQo8foxlDdzSicNJ8smV0QBUZE/dVlbK1ycitZZrGkkRW/uKvsN/FX+lkl6ANb4y8AE2kcn0EarY6FH5NrQgGp5QNbGdQw1/4voFsR8Bji2UHUYuhjCuV14cgenfSQXrW4rVV/YMGaxxR/BbFB5XfZPCBfIsqfVAGH+Rkvy8/ZTBot+v9L1Pp99RZSCCtJoGPYBQagjzEOOAJplnqNHJ1D8bQo+1XElcuki/qxlO3XvXGQwTs2HMyqrnmlfdRjD5JClxx06Wke4y+KjLWtiQfjihCaQK68ixkCF4U8B2lg8nsuFNmUxUMfPCkKir4XfjuZ27y9ZNBnZQNTKOjW1cjU2GhvGyDWfK5xgfSMP/uKpAopSkWYOMaQVeUlFvMI2NtKdkb7lko6bRizEj/EWlUjhTP7TzvVGE76PJh+XeUkcNQmoFBRYOY2L+TYVs9LlKl+DQbcAHOeMYlxfq/zN7OIAh7T5AsgZk=
-    # Use travis setup releases --force (gem install travis) and then anyone with push access can create the token
-  file_glob: true
-  file: "dist/jmonkeyplatform*.*"
-  # - dist/jmonkeyplatform-linux-x64.sh
-  # - dist/jmonkeyplatform-linux-x86.sh
-  # - dist/jmonkeyplatform-windows-x64.exe
-  # - dist/jmonkeyplatform-windows-x86.exe
-  # - dist/jmonkeyplatform.zip
-  # - dist/jmonkeyplatform-macosx.tgz
-  skip_cleanup: true
-  on:
-    repo: jMonkeyEngine/sdk
-    tags: true
-
-before_install:
-  - git fetch --unshallow -q
-  - "[ $TRAVIS_PULL_REQUEST == 'false' ] && openssl aes-256-cbc -K $encrypted_ab3650240afa_key -iv $encrypted_ab3650240afa_iv -in nbproject/private/www-updater.key.enc -out nbproject/private/www-updater.key -d || :"
-  - "[ $TRAVIS_PULL_REQUEST == 'false' ] && chmod 600 nbproject/private/www-updater.key || :"
-  #- "[ $TRAVIS_PULL_REQUEST == 'false' ] && openssl aes-256-cbc -K $encrypted_a1949b55824a_key -iv $encrypted_a1949b55824a_iv -in private/www-updater.key.enc -out private/www-updater.key -d || :"
-
-# before_install:
-  # required libs for android build tools
-  # sudo apt-get update
-  # sudo apt-get install -qq p7zip-full
-  # sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch
-  # newest Android NDK
-  # wget http://dl.google.com/android/ndk/android-ndk-r10c-linux-x86_64.bin -O ndk.bin
-  # 7z x ndk.bin -y > /dev/null
-  # export ANDROID_NDK=`pwd`/android-ndk-r10c
-
-#after_success:
-#  - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives || :'
-#  - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives bintrayUpload || :'

+ 4 - 5
BasicGameTemplate/nbproject/project.properties

@@ -42,15 +42,14 @@ javac.classpath=\
     ${libs.jme3-lwjgl.classpath}:\
     ${libs.jme3-effects.classpath}:\
     ${libs.jme3-terrain.classpath}:\
-    ${libs.jme3-bullet.classpath}:\
-    ${libs.jme3-bullet-native.classpath}
+    ${libs.jme3-jbullet.classpath}
 # Space-separated list of extra javac options
 javac.compilerargs=
 javac.deprecation=false
 javac.processorpath=\
     ${javac.classpath}
-javac.source=1.7
-javac.target=1.7
+javac.source=11
+javac.target=11
 javac.test.classpath=\
     ${javac.classpath}:\
     ${build.classes.dir}
@@ -66,7 +65,7 @@ javadoc.use=true
 javadoc.version=false
 javadoc.windowtitle=
 jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
-jme.project.version=3.1
+jme.project.version=3.4.1
 jnlp.codebase.type=local
 jnlp.descriptor=application
 jnlp.enabled=false

+ 3 - 4
JME3TestsTemplate/nbproject/project.properties

@@ -35,14 +35,13 @@ javac.classpath=\
     ${libs.jme3-niftygui.classpath}:\
     ${libs.jme3-effects.classpath}:\
     ${libs.jme3-terrain.classpath}:\
-    ${libs.jme3-bullet.classpath}:\
-    ${libs.jme3-bullet-native.classpath}:\
+    ${libs.jme3-jbullet.classpath}:\
     ${libs.jme3-test-data.classpath}
 # Space-separated list of extra javac options
 javac.compilerargs=
 javac.deprecation=false
-javac.source=1.7
-javac.target=1.7
+javac.source=11
+javac.target=11
 javac.test.classpath=\
     ${javac.classpath}:\
     ${build.classes.dir}

BIN
branding/core/core.jar/org/netbeans/core/startup/about.png


BIN
branding/core/core.jar/org/netbeans/core/startup/splash.gif


+ 1 - 1
branding/modules/org-netbeans-core.jar/org/netbeans/core/ui/Bundle.properties

@@ -1,3 +1,3 @@
-LBL_Copyright=<p><em>Copyright &copy; 2020 jMonkeyEngine.\n<br>Please visit <a style="color: #f3c802;" href="http://jmonkeyengine.org">http://jmonkeyengine.org</a> for more information.</em></p><p>Icons sets : <ul><li><a style="color: #f3c802;" href="http://brankic1979.com/icons/">Brankic1979</a></li><li><a style="color: #f3c802;" href="http://www.entypo.com/index.php">Entypo+</a></li><li><a style="color: #f3c802;" href="https://hub.jmonkeyengine.org/u/ogli">Ogli</a> (edited by <a style="color: #f3c802;" href="https://hub.jmonkeyengine.org/u/grizeldi">grizeldi</a>)</li><ul> </p>
+LBL_Copyright=<p><em>Copyright &copy; 2022 jMonkeyEngine.\n<br>Please visit <a style="color: #f3c802;" href="http://jmonkeyengine.org">http://jmonkeyengine.org</a> for more information.</em></p><p>Icons sets : <ul><li><a style="color: #f3c802;" href="http://brankic1979.com/icons/">Brankic1979</a></li><li><a style="color: #f3c802;" href="http://www.entypo.com/index.php">Entypo+</a></li><li><a style="color: #f3c802;" href="https://hub.jmonkeyengine.org/u/ogli">Ogli</a> (edited by <a style="color: #f3c802;" href="https://hub.jmonkeyengine.org/u/grizeldi">grizeldi</a>)</li><ul> </p>
 updates_not_found=<p style="margin: 0"><b>Updates:</b> jMonkeyEngine SDK is updated to version {0}</p>\n 
 URL_ON_IMG=http://jmonkeyengine.org/

+ 23 - 26
build.gradle

@@ -3,7 +3,7 @@ import groovy.xml.MarkupBuilder
 import java.util.zip.ZipFile
 
 plugins {
-    id "de.undercouch.download" version "3.4.3"
+    id "de.undercouch.download" version "5.1.0"
 }
 
 apply from: 'gradle/libs/digest.gradle'
@@ -14,9 +14,6 @@ if (!hasProperty('mainClass')) {
 }
 
 repositories {
-    maven {
-        url "http://nifty-gui.sourceforge.net/nifty-maven-repo/"
-    }
     maven { url "https://jitpack.io" }
     maven {
         url "https://maven.google.com/"
@@ -38,29 +35,29 @@ dependencies {
     corelibs dep("com.badlogicgames.gdx:gdx-ai:1.8.1", true, true)
     corelibs dep("javax.help:javahelp:2.0.05", false, false)
 
-    corelibs dep("org.jmonkeyengine:jme3-core:3.4.0-stable", false, false)
-    corelibs dep("org.jmonkeyengine:jme3-desktop:3.4.0-stable", false, false)
-    corelibs dep("org.jmonkeyengine:jme3-lwjgl:3.4.0-stable", false, false)
-    corelibs dep("org.jmonkeyengine:jme3-effects:3.4.0-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-core:3.4.1-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-desktop:3.4.1-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-lwjgl:3.4.1-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-effects:3.4.1-stable", false, false)
     corelibs dep("org.jmonkeyengine:jme3-blender:3.3.2-stable", false, false) // Pin Pointed until jme3-blender has a dedicated release or support is phased out.
     optlibs dep("com.github.stephengold:Minie:4.6.0", false, false) // replacement for bullet-native
     corelibs dep(fileTree("lib"), false, false)
-    corelibs dep("org.jmonkeyengine:jme3-jogg:3.4.0-stable", true, true)
-
-    corelibs dep("org.jmonkeyengine:jme3-networking:3.4.0-stable", false, false)
-    corelibs dep("org.jmonkeyengine:jme3-niftygui:3.4.0-stable", false, false)
-    corelibs dep("org.jmonkeyengine:jme3-plugins:3.4.0-stable", false, false)
-    corelibs dep("org.jmonkeyengine:jme3-terrain:3.4.0-stable", false, false)
-
-    optlibs dep("org.jmonkeyengine:jme3-jbullet:3.4.0-stable", false, false)
-    optlibs dep("org.jmonkeyengine:jme3-android:3.4.0-stable", false, false)
-    optlibs dep("org.jmonkeyengine:jme3-ios:3.4.0-stable", false, false)
-    optlibs dep("org.jmonkeyengine:jme3-android-native:3.4.0-stable", false, false)
-    optlibs dep("org.jmonkeyengine:jme3-lwjgl3:3.4.0-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-jogg:3.4.1-stable", true, true)
+
+    corelibs dep("org.jmonkeyengine:jme3-networking:3.4.1-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-niftygui:3.4.1-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-plugins:3.4.1-stable", false, false)
+    corelibs dep("org.jmonkeyengine:jme3-terrain:3.4.1-stable", false, false)
+
+    optlibs dep("org.jmonkeyengine:jme3-jbullet:3.4.1-stable", false, false)
+    optlibs dep("org.jmonkeyengine:jme3-android:3.4.1-stable", false, false)
+    optlibs dep("org.jmonkeyengine:jme3-ios:3.4.1-stable", false, false)
+    optlibs dep("org.jmonkeyengine:jme3-android-native:3.4.1-stable", false, false)
+    optlibs dep("org.jmonkeyengine:jme3-lwjgl3:3.4.1-stable", false, false)
     optlibs dep("com.github.stephengold:Heart:7.1.0", false, false)
     optlibs dep("com.github.stephengold:Wes:0.6.7", false, false)
-    testdatalibs dep("org.jmonkeyengine:jme3-testdata:3.4.0-stable", false, false)
-    examplelibs dep("org.jmonkeyengine:jme3-examples:3.4.0-stable", false, false)
+    testdatalibs dep("org.jmonkeyengine:jme3-testdata:3.4.1-stable", false, false)
+    examplelibs dep("org.jmonkeyengine:jme3-examples:3.4.1-stable", false, true)
 }
 
 def dep(coords, javadoc = false, sources = false) {
@@ -85,7 +82,7 @@ task checkPlatformConfig {
              * def f = file("netbeans.zip")
              * new URL(netbeansUrl).withInputStream{ i -> f.withOutputStream{ it << i }}
              */
-            download {
+            download.run {
                 src netbeansUrl
                 dest "netbeans.zip"
                 overwrite false // however if overwrite matters the build failed anyway
@@ -631,8 +628,8 @@ task fixPlatformIndependent(dependsOn: patchPlatformIndependent, type: Zip) {
     description = "We compile our installers with the bundled jdk, but the platform independent zip doesn't have the jdk. For this we need to change the jmonkeyplatform.zip after building the installers to not have a jdk bundled"
 
     from 'dist/temp'
-    archiveName = 'jmonkeyplatform.zip'
-    destinationDir = file('dist')
+    archiveFileName = 'jmonkeyplatform.zip'
+    destinationDirectory = file('dist')
 
     doLast {
         delete("dist/temp")
@@ -640,7 +637,7 @@ task fixPlatformIndependent(dependsOn: patchPlatformIndependent, type: Zip) {
 }
 
 wrapper {
-    gradleVersion = '5.6.4'
+    gradleVersion = '6.9.2'
 }
 
 //jar.dependsOn(buildSdk)

+ 2 - 2
gradle.properties

@@ -1,5 +1,5 @@
 # Version number used for plugins, only 3 numbers (e.g. 3.1.3)
-jmeVersion = 3.4.0
+jmeVersion = 3.4.1
 # Version used for application and settings folder, no spaces!
 jmeMainVersion = 3.4
 # Version addition pre-alpha-svn, Stable, Beta
@@ -14,4 +14,4 @@ buildJavaDoc = true
 buildSdkProject = true
 
 # Path for downloading NetBeans Base
-netbeansUrl = https://downloads.apache.org/netbeans/netbeans/12.6/netbeans-12.6-bin.zip
+netbeansUrl = https://dlcdn.apache.org/netbeans/netbeans/14/netbeans-14-bin.zip

BIN
gradle/wrapper/gradle-wrapper.jar


+ 1 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 153 - 107
gradlew

@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
 
 #
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,78 +17,113 @@
 #
 
 ##############################################################################
-##
-##  Gradle start up script for UN*X
-##
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
 ##############################################################################
 
 # Attempt to set APP_HOME
+
 # Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
 done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
 
 APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
+APP_BASE_NAME=${0##*/}
 
 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
 
 warn () {
     echo "$*"
-}
+} >&2
 
 die () {
     echo
     echo "$*"
     echo
     exit 1
-}
+} >&2
 
 # OS specific support (must be 'true' or 'false').
 cygwin=false
 msys=false
 darwin=false
 nonstop=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-  NONSTOP* )
-    nonstop=true
-    ;;
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
 esac
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
+
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
         # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
+        JAVACMD=$JAVA_HOME/jre/sh/java
     else
-        JAVACMD="$JAVA_HOME/bin/java"
+        JAVACMD=$JAVA_HOME/bin/java
     fi
     if [ ! -x "$JAVACMD" ] ; then
         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
     fi
 else
-    JAVACMD="java"
+    JAVACMD=java
     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
@@ -105,84 +140,95 @@ location of your Java installation."
 fi
 
 # Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
 fi
 
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
 
 # For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-    JAVACMD=`cygpath --unix "$JAVACMD"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
     # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
         fi
-        i=$((i+1))
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
     done
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
 fi
 
-# Escape application args
-save () {
-    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
-    echo " "
-}
-APP_ARGS=$(save "$@")
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
 
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
 
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
-  cd "$(dirname "$0")"
-fi
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
 
 exec "$JAVACMD" "$@"

+ 7 - 18
gradlew.bat

@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
 set APP_BASE_NAME=%~n0
 set APP_HOME=%DIRNAME%
 
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
 
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
 
 set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
 
 echo.
 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
 set JAVA_HOME=%JAVA_HOME:"=%
 set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
 
 echo.
 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
 
 goto fail
 
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
 :execute
 @rem Setup the command line
 
 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 
+
 @rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
 
 :end
 @rem End local scope for the variables with windows NT shell

BIN
harness-override/app.exe


BIN
harness-override/app64.exe


+ 5 - 5
harness-override/override.properties

@@ -1,6 +1,6 @@
-app.exe.hashBefore=0f33727f20bc36f24345da0d1b586e47ce0ade0596df7c2a6d3a7dcae1c19374
-app.exe.hashAfter=fe6b4a2d7aaddb9c0a84c94e1553b43bd11fa468b1079cabdca82e7dea731755
-app64.exe.hashBefore=fe9c568a5ddf34944f11b820f7d3fa9c6448b14b825a49301697b4bd080de279
-app64.exe.hashAfter=f60bc962278fb5b407a7cbe86281185117b76a84e00576496d2559175843bd50
+app.exe.hashBefore=08568a9ece42e67685df6f20a0383e70248bb0e41c1ab2ec45883bbe001b6326
+app.exe.hashAfter=c399d6eb732faadb7c2850451fd569e4854f5ae7250c66051043aca9f47b22ca
+app64.exe.hashBefore=ee790adfcf8436d7b128ddb43d6f6df1a8404df7612972b2bef7022b4f27bf69
+app64.exe.hashAfter=b50451b5b264504cb61998a8363cc7fbefe6db4d7df55c2b490cc293ede3bfe2
 pre7_app.exe.hashBefore=f4ea42a1ec2572b2a65a6d9baef0dd1f7293980c44c5d031c5b2ea5752352e49
-pre7_app.exe.hashAfter=fa8ae6457dad70a65cecb623ef0f60a86c92e856705eaeb2f22e43310fa9226f
+pre7_app.exe.hashAfter=fa8ae6457dad70a65cecb623ef0f60a86c92e856705eaeb2f22e43310fa9226f

+ 2 - 7
jdks/download-jdks.sh

@@ -11,7 +11,7 @@
 set -e # Quit on Error
 
 jdk_major_version="11"
-jdk_version="0.6"
+jdk_version="0.15"
 jdk_build_version="10"
 platforms=( "x64_linux" "x86-32_windows" "x64_windows" "x64_mac" )
 
@@ -72,12 +72,7 @@ function download_jdk {
     then
         echo "<<< Already existing, SKIPPING."
     else
-        if [ "$jdk_major_version" == "8" ];
-        then
-            curl -s -o downloads/jdk-$1$2 -L https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk$jdk_version-$jdk_build_version/OpenJDK8U-jdk_$1_hotspot_$jdk_version$jdk_build_version$2
-        else
-            curl -s -o downloads/jdk-$1$2 -L https://github.com/AdoptOpenJDK/openjdk$jdk_major_version-binaries/releases/download/jdk-$jdk_major_version.$jdk_version+$jdk_build_version/OpenJDK$jdk_major_version\U-jdk_$1_hotspot_$jdk_major_version.$jdk_version\_$jdk_build_version$2
-        fi
+        curl -s -o downloads/jdk-$1$2 -L https://github.com/adoptium/temurin$jdk_major_version-binaries/releases/download/jdk-$jdk_major_version.$jdk_version+$jdk_build_version/OpenJDK${jdk_major_version}U-jdk_$1_hotspot_$jdk_major_version.${jdk_version}_$jdk_build_version$2
         echo "<<< OK!"
     fi
 }

+ 173 - 72
jme3-core/src/com/jme3/gde/core/assets/ExternalChangeScanner.java

@@ -1,22 +1,22 @@
 /*
  * Copyright (c) 2003-2012 jMonkeyEngine
  * All rights reserved.
- * 
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
  * met:
- * 
+ *
  * * Redistributions of source code must retain the above copyright
  *   notice, this list of conditions and the following disclaimer.
- * 
+ *
  * * Redistributions in binary form must reproduce the above copyright
  *   notice, this list of conditions and the following disclaimer in the
  *   documentation and/or other materials provided with the distribution.
- * 
+ *
  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
  *   may be used to endorse or promote products derived from this software
  *   without specific prior written permission.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
@@ -34,12 +34,23 @@ package com.jme3.gde.core.assets;
 import com.jme3.export.Savable;
 import com.jme3.gde.core.scene.ApplicationLogHandler;
 import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.core.util.SpatialUtil;
+import com.jme3.gde.core.util.TaggedSpatialFinder;
+import com.jme3.gde.core.util.datatransfer.CopyAnimationDataFromOriginal;
+import com.jme3.gde.core.util.datatransfer.CopyMaterialDataFromOriginal;
+import com.jme3.gde.core.util.datatransfer.CopyMeshDataFromOriginal;
+import com.jme3.gde.core.util.datatransfer.CopyTransformDataFromOriginal;
 import com.jme3.scene.Spatial;
+import java.io.IOException;
+
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+
 import org.netbeans.api.progress.ProgressHandle;
 import org.openide.DialogDisplayer;
 import org.openide.NotifyDescriptor;
@@ -51,6 +62,7 @@ import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileRenameEvent;
 import org.openide.loaders.DataObject;
 import org.openide.loaders.DataObjectNotFoundException;
+import org.openide.nodes.Node;
 import org.openide.util.Exceptions;
 
 /**
@@ -59,16 +71,19 @@ import org.openide.util.Exceptions;
  *
  * @author normenhansen
  */
-public class ExternalChangeScanner implements AssetDataPropertyChangeListener, FileChangeListener {
+public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
+        FileChangeListener {
 
-    private static final Logger logger = Logger.getLogger(ExternalChangeScanner.class.getName());
+    private static final Logger LOGGER =
+            Logger.getLogger(ExternalChangeScanner.class.getName());
     private static final AtomicBoolean userNotified = new AtomicBoolean(false);
-    protected final AssetDataObject assetDataObject;
-    protected final AssetData assetData;
-    protected FileObject originalObject;
+    private final AssetDataObject assetDataObject;
+    private final AssetData assetData;
+    private FileObject originalObject;
 
     public ExternalChangeScanner(AssetDataObject assetDataObject) {
         this.assetDataObject = assetDataObject;
+
         assetData = assetDataObject.getLookup().lookup(AssetData.class);
         if (assetData != null) {
             String path = assetData.getProperty("ORIGINAL_PATH");
@@ -80,88 +95,151 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener, F
             assetDataObject.getPrimaryFile().addFileChangeListener(new FileChangeAdapter() {
                 @Override
                 public void fileDeleted(FileEvent fe) {
-                    logger.log(Level.INFO, "File {0} deleted, remove!", new Object[]{fe.getFile()});
+                    LOGGER.log(Level.INFO, "File {0} deleted, remove!",
+                            new Object[]{fe.getFile()});
                     assetData.removePropertyChangeListener(main);
                     fe.getFile().removeFileChangeListener(this);
                     if (originalObject != null) {
-                        logger.log(Level.INFO, "Remove file change listener for {0}", originalObject);
+                        LOGGER.log(Level.INFO, "Remove file change listener "
+                                + "for {0}", originalObject);
                         originalObject.removeFileChangeListener(main);
                         originalObject = null;
                     }
                 }
             });
         } else {
-            logger.log(Level.WARNING, "Trying to observer changes for asset {0} which has no AssetData in Lookup.", assetDataObject.getName());
+            LOGGER.log(Level.WARNING, "Trying to observer changes for asset "
+                            + "{0} which has no AssetData in Lookup.",
+                    assetDataObject.getName());
         }
     }
 
     private void notifyUser() {
         if (!userNotified.getAndSet(true)) {
             //TODO: execute on separate thread?
-            java.awt.EventQueue.invokeLater(new Runnable() {
-                public void run() {
-                    NotifyDescriptor.Confirmation mesg = new NotifyDescriptor.Confirmation("Original file for " + assetDataObject.getName() + " changed\nTry and reapply mesh data to j3o file?",
-                            "Original file changed",
-                            NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.QUESTION_MESSAGE);
-                    DialogDisplayer.getDefault().notify(mesg);
-                    if (mesg.getValue() != NotifyDescriptor.Confirmation.YES_OPTION) {
-                        userNotified.set(false);
-                        return;
-                    }
-                    SceneApplication.getApplication().enqueue(new Callable<Void>() {
-                        public Void call() throws Exception {
-                            applyExternalData();
-                            return null;
-                        }
-                    });
+            java.awt.EventQueue.invokeLater(() -> {
+                final String noOption = "No";
+                final String allOption = "All";
+                final String meshOption = "Only mesh data";
+                final String animOption = "Only animation data";
+                final NotifyDescriptor.Confirmation message =
+                        new NotifyDescriptor.Confirmation("Original file for "
+                                + assetDataObject.getName() + " changed\nTry "
+                                + "and reapply data to j3o file?",
+                                "Original file changed",
+                                NotifyDescriptor.YES_NO_CANCEL_OPTION,
+                                NotifyDescriptor.QUESTION_MESSAGE);
+                message.setOptions(new Object[]{allOption, meshOption, animOption,
+                        noOption});
+                DialogDisplayer.getDefault().notify(message);
+                if (message.getValue().equals(noOption)) {
                     userNotified.set(false);
+                    return;
                 }
+                SceneApplication.getApplication().enqueue((Callable<Void>) () -> {
+                    applyExternalData(message.getValue().equals(meshOption), 
+                            message.getValue().equals(animOption));
+                    return null;
+                });
+                userNotified.set(false);
             });
         } else {
-            logger.log(Level.INFO, "User already notified about change in {0}", assetDataObject.getName());
+            LOGGER.log(Level.INFO, "User already notified about change in "
+                    + "{0}", assetDataObject.getName());
         }
     }
-
-    private void applyExternalData() {
-        ProgressHandle handle = ProgressHandle.createHandle("Updating file data");
+    
+    private void applyExternalData(final boolean onlyMeshData, 
+            final boolean onlyAnimData) {
+        final ProgressHandle handle = ProgressHandle.createHandle("Updating "
+                + "file "
+                + "data");
         handle.start();
         try {
-            Spatial original = loadOriginalSpatial();
-            Spatial spat = (Spatial) assetDataObject.loadAsset();
-
-            SpatialUtil.updateMeshDataFromOriginal(spat, original);
-            SpatialUtil.updateMaterialDataFromOriginal(spat, original);
-            if (SpatialUtil.hasAnimations(original)) {
-                NotifyDescriptor.Confirmation mesg = new NotifyDescriptor.Confirmation("Model appears to have animations, try to import as well?\nCurrently this will unlink attachment Nodes and clear\nadded effects tracks.",
-                        "Animations Available",
-                        NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.QUESTION_MESSAGE);
-                DialogDisplayer.getDefault().notify(mesg);
-                if (mesg.getValue() == NotifyDescriptor.Confirmation.YES_OPTION) {
-                    SpatialUtil.updateAnimControlDataFromOriginal(spat, original);
-                }
+            final Spatial original = loadOriginalSpatial();
+            final Spatial spat = (Spatial) assetDataObject.loadAsset();
+            final TaggedSpatialFinder finder = new TaggedSpatialFinder();
+
+            if(!onlyMeshData && SpatialUtil.hasAnimations(original)) {
+                new CopyAnimationDataFromOriginal(finder).update(spat,
+                            original);
+                
             }
+            if(!onlyAnimData){
+                new CopyMeshDataFromOriginal(finder).update(spat, original);
+            }
+            if (!onlyMeshData && !onlyAnimData) {
+                new CopyTransformDataFromOriginal(finder).update(spat, original);
+                new CopyMaterialDataFromOriginal(finder).update(spat, original);
+            }
+            // Do a complicated recurse refresh since AbstractSceneExplorerNode:refresh() isn't working
+            SceneApplication.getApplication().enqueue((Runnable) () -> {
+                Node rootNode = SceneExplorerTopComponent.findInstance().getExplorerManager().getRootContext();
+                if (rootNode instanceof JmeNode) {
+                    refreshNamedSpatial((JmeNode) rootNode, spat.getName());
+                }
+            });
             closeOriginalSpatial();
             assetDataObject.saveAsset();
-        } catch (Exception e) {
-            logger.log(Level.SEVERE, "Exception when trying to update external data.", e);
+        } catch (IOException e) {
+            LOGGER.log(Level.SEVERE, "Exception when trying to update "
+                    + "external data.", e);
         } finally {
             handle.finish();
         }
     }
+    
+    /**
+     * Look for the spatial to update using the name of the asset
+     * @param spatial
+     * @param name 
+     */
+    private void refreshNamedSpatial(JmeSpatial spatial, String name){
+        if(spatial.getName().equals(name)){
+            recurseRefresh(spatial);
+        } else {
+            for(Node s: spatial.getChildren().getNodes()){
+                if(s instanceof JmeSpatial){
+                    refreshNamedSpatial((JmeSpatial) s, name);
+                }
+                
+            }
+        }
+    }
+    
+    /**
+     * Refreshes the spatial and all children
+     * @param spatial 
+     */
+    private void recurseRefresh(JmeSpatial spatial){
+        spatial.refresh(false);
+        for(Node s: spatial.getChildren().getNodes()){
+            if(s instanceof JmeSpatial){
+                recurseRefresh((JmeSpatial) s);
+            }
+        }
+    }
 
     private Spatial loadOriginalSpatial() {
         try {
-            DataObject dobj = DataObject.find(originalObject);
-            AssetData originalAssetData = dobj.getLookup().lookup(AssetData.class);
+            final DataObject dobj = DataObject.find(originalObject);
+            final AssetData originalAssetData =
+                    dobj.getLookup().lookup(AssetData.class);
             if (originalAssetData != null) {
-                Savable sav = originalAssetData.loadAsset();
+                final Savable sav = originalAssetData.loadAsset();
                 if (sav instanceof Spatial) {
                     return (Spatial) sav;
                 } else {
-                    logger.log(Level.SEVERE, "Trying to load original for {0} but it is not a Spatial: {1}", new Object[]{assetDataObject.getName(), originalObject});
+                    LOGGER.log(Level.SEVERE, "Trying to load original for {0}"
+                                    + " but it is not a Spatial: {1}",
+                            new Object[]{assetDataObject.getName(),
+                                    originalObject});
                 }
             } else {
-                logger.log(Level.WARNING, "Could not get AssetData for {0}, original file {1}", new Object[]{assetDataObject.getName(), originalObject});
+                LOGGER.log(Level.WARNING, "Could not get AssetData for {0}, "
+                                + "original file {1}",
+                        new Object[]{assetDataObject.getName(),
+                                originalObject});
             }
         } catch (DataObjectNotFoundException ex) {
             Exceptions.printStackTrace(ex);
@@ -171,12 +249,16 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener, F
 
     private Spatial closeOriginalSpatial() {
         try {
-            DataObject dobj = DataObject.find(originalObject);
-            AssetData originalAssetData = dobj.getLookup().lookup(AssetData.class);
+            final DataObject dobj = DataObject.find(originalObject);
+            final AssetData originalAssetData =
+                    dobj.getLookup().lookup(AssetData.class);
             if (originalAssetData != null) {
                 originalAssetData.closeAsset();
             } else {
-                logger.log(Level.WARNING, "Could not get AssetData for {0}, original file {1}", new Object[]{assetDataObject.getName(), originalObject});
+                LOGGER.log(Level.WARNING, "Could not get AssetData for {0}, "
+                                + "original file {1}",
+                        new Object[]{assetDataObject.getName(),
+                                originalObject});
             }
         } catch (DataObjectNotFoundException ex) {
             Exceptions.printStackTrace(ex);
@@ -184,36 +266,49 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener, F
         return null;
     }
 
-    private void setObservedFilePath(String assetName) {
-        ProjectAssetManager mgr = assetDataObject.getLookup().lookup(ProjectAssetManager.class);
+    private void setObservedFilePath(final String assetName) {
+        final ProjectAssetManager mgr =
+                assetDataObject.getLookup().lookup(ProjectAssetManager.class);
         if (mgr == null) {
-            logger.log(Level.WARNING, "File is not part of a jME project but tries to find original model...");
+            LOGGER.log(Level.WARNING, "File is not part of a jME project but "
+                    + "tries to find original model...");
             return;
         }
-        FileObject fileObject = mgr.getAssetFileObject(assetName);
+        final FileObject fileObject = mgr.getAssetFileObject(assetName);
         //ignoring same file -> old properties files
         if (fileObject != null) {
             if (!fileObject.equals(assetDataObject.getPrimaryFile())) {
                 if (originalObject != null) {
                     originalObject.removeFileChangeListener(this);
-                    logger.log(Level.INFO, "{0} stops listening for external changes on {1}", new Object[]{assetDataObject.getName(), originalObject});
+                    LOGGER.log(Level.INFO, "{0} stops listening for external "
+                                    + "changes on {1}",
+                            new Object[]{assetDataObject.getName(),
+                                    originalObject});
                 }
                 fileObject.addFileChangeListener(this);
-                logger.log(Level.INFO, "{0} listening for external changes on {1}", new Object[]{assetDataObject.getName(), fileObject});
+                LOGGER.log(Level.INFO, "{0} listening for external changes on"
+                        + " {1}", new Object[]{assetDataObject.getName(),
+                        fileObject});
                 originalObject = fileObject;
             } else {
-                logger.log(Level.FINE, "Ignoring old reference to self for {0}", assetDataObject.getName());
+                LOGGER.log(Level.FINE, "Ignoring old reference to self for "
+                        + "{0}", assetDataObject.getName());
             }
         } else {
-            logger.log(Level.INFO, "Could not get FileObject for {0} when trying to update original data for {1}. Possibly deleted.", new Object[]{assetName, assetDataObject.getName()});
+            LOGGER.log(Level.INFO, "Could not get FileObject for {0} when "
+                    + "trying to update original data for {1}. Possibly deleted"
+                    + ".", new Object[]{assetName, assetDataObject.getName()});
             //TODO: add folder listener for when recreated
         }
     }
 
     @Override
-    public void assetDataPropertyChanged(String property, String before, String after) {
-        if ("ORIGINAL_PATH".equals(property)) {
-            logger.log(Level.INFO, "Notified about change in AssetData properties for {0}", assetDataObject.getName());
+    public void assetDataPropertyChanged(final String property,
+                                         final String before,
+                                         final String after) {
+        if (SpatialUtil.ORIGINAL_PATH.equals(property)) {
+            LOGGER.log(Level.INFO, "Notified about change in AssetData "
+                    + "properties for {0}", assetDataObject.getName());
             setObservedFilePath(after);
         }
     }
@@ -225,14 +320,18 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener, F
     }
 
     public void fileChanged(FileEvent fe) {
-        logger.log(Level.INFO, "External file {0} for {1} changed!", new Object[]{fe.getFile(), assetDataObject.getName()});
+        LOGGER.log(Level.INFO, "External file {0} for {1} changed!",
+                new Object[]{fe.getFile(), assetDataObject.getName()});
         notifyUser();
     }
 
     public void fileDeleted(FileEvent fe) {
-        logger.log(Level.INFO, "External file {0} for {1} deleted!", new Object[]{fe.getFile(), assetDataObject.getName()});
+        LOGGER.log(Level.INFO, "External file {0} for {1} deleted!",
+                new Object[]{fe.getFile(), assetDataObject.getName()});
         if (originalObject != null) {
-            logger.log(ApplicationLogHandler.LogLevel.INFO, "Remove file change listener for deleted object on {0}", assetDataObject.getName());
+            LOGGER.log(ApplicationLogHandler.LogLevel.INFO, "Remove file "
+                            + "change listener for deleted object on {0}",
+                    assetDataObject.getName());
             originalObject.removeFileChangeListener(this);
             originalObject = null;
         }
@@ -240,9 +339,11 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener, F
     }
 
     public void fileRenamed(FileRenameEvent fe) {
-        logger.log(Level.INFO, "External file {0} for {1} renamed!", new Object[]{fe.getFile(), assetDataObject.getName()});
+        LOGGER.log(Level.INFO, "External file {0} for {1} renamed!",
+                new Object[]{fe.getFile(), assetDataObject.getName()});
         if (originalObject != null) {
-            logger.log(Level.INFO, "Remove file change listener for renamed object on {0}", assetDataObject.getName());
+            LOGGER.log(Level.INFO, "Remove file change listener for renamed "
+                    + "object on {0}", assetDataObject.getName());
             originalObject.removeFileChangeListener(this);
             originalObject = null;
         }

+ 8 - 1
jme3-core/src/com/jme3/gde/core/editor/nodes/Diagram.java

@@ -62,6 +62,7 @@ import javax.swing.border.TitledBorder;
 /**
  * The Diagram is the main canvas where all nodes {@link NodePanel} and
  * their connections {@link ConnectionEndpoint} {@link Connection} are added onto.
+ * 
  * @author Nehon
  */
 public abstract class Diagram extends JPanel implements MouseListener, 
@@ -272,7 +273,7 @@ public abstract class Diagram extends JPanel implements MouseListener,
         repaint();
         parent.notifyRemoveNode(node);
     }
-
+    
     public List<Selectable> getSelectedItems() {
         return selectedItems;
     }
@@ -558,6 +559,12 @@ public abstract class Diagram extends JPanel implements MouseListener,
      */
     public abstract void autoLayout();
     
+    /**
+     * This toggles continuous updates for material previews
+     * @param on
+     */
+    public abstract void toggleUpdateThread(boolean on);
+    
     @Override
     public void componentResized(ComponentEvent e) {
         minWidth = e.getComponent().getWidth() - 2;

+ 2 - 2
jme3-core/src/com/jme3/gde/core/editor/nodes/NodePanel.java

@@ -207,8 +207,8 @@ public abstract class NodePanel extends DraggablePanel implements Selectable, Ke
     @Override
     public abstract String getKey(); // satisfy Selectable interface
     
-    public void cleanup(){
-        if (toolBar != null) {
+    public void cleanup() {
+        if (toolBar != null && toolBar.getParent() != null) {
             toolBar.getParent().remove(toolBar);
         }
     }

+ 28 - 0
jme3-core/src/com/jme3/gde/core/scene/controller/SceneToolController.java

@@ -67,6 +67,22 @@ public class SceneToolController extends AbstractAppState {
     protected AssetManager manager;
     protected Material blueMat;
     protected AbstractCameraController camController;
+    
+    private SceneToolControllerListener toolListener;
+    
+    /**
+     * Expandable interface to send callbacks to (primarily) gui. 
+     */
+    public interface SceneToolControllerListener {
+        
+        /**
+         * Called when cursor's location changes.
+         *
+         * @param location location for cursor
+         */
+        public void onSetCursorLocation(Vector3f location);
+    }
+    
 
     @SuppressWarnings("LeakingThisInConstructor")
     public SceneToolController(AssetManager manager) {
@@ -193,6 +209,9 @@ public class SceneToolController extends AbstractAppState {
         if (camController != null) {
             camController.doSetCamFocus(location);
         }
+        if (toolListener != null) {
+            toolListener.onSetCursorLocation(location);
+        }
     }
 
     public void snapCursorToSelection() {
@@ -428,4 +447,13 @@ public class SceneToolController extends AbstractAppState {
     public Spatial getSelectionShape() {
         return selectionShape;
     }
+    
+    /**
+     * Set the listener to receive callbacks from this class.
+     * 
+     * @param listener listener
+     */
+    public void setToolListener(final SceneToolControllerListener listener) {
+        this.toolListener = listener;
+    }
 }

+ 3 - 1
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeMesh.java

@@ -97,7 +97,9 @@ public class JmeMesh extends AbstractSceneExplorerNode{
 
         set.put(makeProperty(obj, int.class, "getId", "setId", "Id"));
         set.put(makeProperty(obj, Mesh.Mode.class, "getMode", "setMode", "Mode"));
-        set.put(makeProperty(obj, float.class, "getPointSize", "setPointSize", "Point Size"));
+        //Removed below property because it caused a nullpointer exception 
+        //because the getter and setter does not exist anymore, it was deprecated
+        //set.put(makeProperty(obj, float.class, "getPointSize", "setPointSize", "Point Size"));
 
         sheet.put(set);
         return sheet;

+ 7 - 3
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeSpatialChildren.java

@@ -83,11 +83,15 @@ public class JmeSpatialChildren extends Children.Keys<Object> {
         setKeys(createKeys());
         refresh();
         
-        for (Node n : nodes) { // TODO: Why is nodes always empty? What about the Children's Children?
-            if (n instanceof AbstractSceneExplorerNode) {
-                ((AbstractSceneExplorerNode)n).refresh(immediate);
+        if (nodes != null) { //Added a null check just to make sure the UI don't crash
+            
+            for (Node n : nodes) { // TODO: Why is nodes always empty? What about the Children's Children?
+                if (n instanceof AbstractSceneExplorerNode) {
+                    ((AbstractSceneExplorerNode)n).refresh(immediate);
+                }
             }
         }
+
     }
 
     public void setReadOnly(boolean cookie) {

+ 42 - 285
jme3-core/src/com/jme3/gde/core/util/SpatialUtil.java

@@ -1,22 +1,22 @@
 /*
  * Copyright (c) 2003-2012 jMonkeyEngine
  * All rights reserved.
- * 
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
  * met:
- * 
+ *
  * * Redistributions of source code must retain the above copyright
  *   notice, this list of conditions and the following disclaimer.
- * 
+ *
  * * Redistributions in binary form must reproduce the above copyright
  *   notice, this list of conditions and the following disclaimer in the
  *   documentation and/or other materials provided with the distribution.
- * 
+ *
  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
  *   may be used to endorse or promote products derived from this software
  *   without specific prior written permission.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
@@ -31,14 +31,9 @@
  */
 package com.jme3.gde.core.util;
 
-import com.jme3.animation.AnimControl;
-import com.jme3.animation.SkeletonControl;
-import com.jme3.gde.core.scene.ApplicationLogHandler.LogLevel;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Node;
-import com.jme3.scene.SceneGraphVisitor;
-import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.anim.AnimComposer;
 import com.jme3.scene.Spatial;
+
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
@@ -51,329 +46,91 @@ import java.util.logging.Logger;
  *
  * @author normenhansen
  */
-@SuppressWarnings({"unchecked", "rawtypes"})
 public class SpatialUtil {
 
-    private static final Logger logger = Logger.getLogger(SpatialUtil.class.getName());
-    //TODO: use these variables
     public static final String ORIGINAL_NAME = "ORIGINAL_NAME";
     public static final String ORIGINAL_PATH = "ORIGINAL_PATH";
+    
+    private static final Logger LOGGER =
+            Logger.getLogger(SpatialUtil.class.getName());
 
     /**
      * Gets a "pathname" for the given Spatial, combines the Spatials and
      * parents names to make a long name. This "path" is stored in geometry
      * after the first import for example.
      *
-     * @param spat
-     * @return
+     * @param spat Spatial
+     * @return id of spatial
      */
     public static String getSpatialPath(Spatial spat) {
         StringBuilder geometryIdentifier = new StringBuilder();
         while (spat != null) {
             String name = spat.getName();
             if (name == null) {
-                logger.log(Level.WARNING, "Null spatial name!");
+                LOGGER.log(Level.WARNING, "Null spatial name!");
                 name = "null";
             }
             geometryIdentifier.insert(0, name);
             geometryIdentifier.insert(0, '/');
             spat = spat.getParent();
         }
-        String id = geometryIdentifier.toString();
-        return id;
+        return geometryIdentifier.toString();
     }
 
     /**
      * Stores ORIGINAL_NAME and ORIGINAL_PATH UserData to given Spatial and all
      * sub-Spatials.
      *
-     * @param spat
+     * @param spat spatial
      */
     public static void storeOriginalPathUserData(Spatial spat) {
         //TODO: only stores for geometry atm
-        final ArrayList<String> geomMap = new ArrayList<String>();
+        final ArrayList<String> geomMap = new ArrayList<>();
         if (spat != null) {
-            spat.depthFirstTraversal(new SceneGraphVisitor() {
-                @Override
-                public void visit(Spatial geom) {
-                    Spatial curSpat = geom;
-                    String geomName = curSpat.getName();
-                    if (geomName == null) {
-                        logger.log(Level.WARNING, "Null Spatial name!");
-                        geomName = "null";
-                    }
-                    geom.setUserData("ORIGINAL_NAME", geomName);
-                    logger.log(Level.FINE, "Set ORIGINAL_NAME for {0}", geomName);
-                    String id = SpatialUtil.getSpatialPath(curSpat);
-                    if (geomMap.contains(id)) {
-                        logger.log(Level.WARNING, "Cannot create unique name for Spatial {0}: {1}", new Object[]{geom, id});
-                    }
-                    geomMap.add(id);
-                    geom.setUserData("ORIGINAL_PATH", id);
-                    logger.log(Level.FINE, "Set ORIGINAL_PATH for {0}", id);
-                }
-            });
-        } else {
-            logger.log(Level.SEVERE, "No Spatial available when trying to add Spatial paths.");
-        }
-    }
-
-    /**
-     * Finds a previously marked spatial in the supplied root Spatial, creates
-     * the name and path to be looked for from the given needle Spatial.
-     *
-     * @param root
-     * @param needle
-     * @return
-     */
-    public static Spatial findTaggedSpatial(final Spatial root, final Spatial needle) {
-        if (needle == null) {
-            logger.log(Level.WARNING, "Trying to find null needle for {0}", root);
-            return null;
-        }
-        final String name = needle.getName();
-        final String path = getSpatialPath(needle);
-        if (name == null || path == null) {
-            logger.log(Level.WARNING, "Trying to find tagged Spatial with null name spatial for {0}.", root);
-            return null;
-        }
-        final Class clazz = needle.getClass();
-        String rootName = root.getUserData("ORIGINAL_NAME");
-        String rootPath = root.getUserData("ORIGINAL_PATH");
-        if (name.equals(rootName) && path.equals(rootPath)) {
-            return root;
-        }
-        final SpatialHolder holder = new SpatialHolder();
-        root.depthFirstTraversal(new SceneGraphVisitor() {
-            @Override
-            public void visit(Spatial spatial) {
-                String spName = spatial.getUserData("ORIGINAL_NAME");
-                String spPath = spatial.getUserData("ORIGINAL_PATH");
-                if (name.equals(spName) && path.equals(spPath) && clazz.isInstance(spatial)) {
-                    if (holder.spatial == null) {
-                        holder.spatial = spatial;
-                    } else {
-                        logger.log(Level.WARNING, "Found Spatial {0} twice in {1}", new Object[]{path, root});
-                    }
-                }
-            }
-        });
-        return holder.spatial;
-    }
-
-    /**
-     * Finds a spatial in the given Spatial tree with the specified name and
-     * path, the path and name are constructed from the given (sub-)spatial(s)
-     * and is not read from the UserData of the objects. This is mainly used to
-     * check if the original spatial still exists in the original file.
-     *
-     * @param root
-     * @param name
-     * @param path
-     */
-    public static Spatial findSpatial(final Spatial root, final String name, final String path) {
-        if (name == null || path == null) {
-            logger.log(Level.WARNING, "Trying to find Spatial with null name spatial for {0}.", root);
-            return null;
-        }
-        if (name.equals(root.getName()) && path.equals(getSpatialPath(root))) {
-            return root;
-        }
-        final SpatialHolder holder = new SpatialHolder();
-        root.depthFirstTraversal(new SceneGraphVisitor() {
-            @Override
-            public void visit(Spatial spatial) {
-                if (name.equals(spatial.getName()) && path.equals(getSpatialPath(spatial))) {
-                    if (holder.spatial == null) {
-                        holder.spatial = spatial;
-                    } else {
-                        logger.log(Level.WARNING, "Found Spatial {0} twice in {1}", new Object[]{path, root});
-                    }
-                }
-            }
-        });
-        return holder.spatial;
-    }
-
-    /**
-     * Updates the mesh data of existing objects from an original file, adds new
-     * nonexisting geometry objects to the root, including their parents if they
-     * don't exist.
-     *
-     * @param root
-     * @param original
-     */
-    public static void updateMeshDataFromOriginal(final Spatial root, final Spatial original) {
-        //loop through original to also find new geometry
-        original.depthFirstTraversal(new SceneGraphVisitorAdapter() {
-            @Override
-            public void visit(Geometry geom) {
-                //will always return same class type as 2nd param, so casting is safe
-                Geometry spat = (Geometry) findTaggedSpatial(root, geom);
-                if (spat != null) {
-                    spat.setMesh(geom.getMesh());
-                    logger.log(LogLevel.USERINFO, "Updated mesh for Geometry {0}", geom.getName());
-                } else {
-                    addLeafWithNonExistingParents(root, geom);
-                }
-            }
-        });
-    }
+            spat.depthFirstTraversal(geom -> {
 
-    /**
-     * Updates material of existing objects from an original file.
-     *
-     * @param root
-     * @param original
-     */
-    public static void updateMaterialDataFromOriginal(final Spatial root,
-                                                      final Spatial original) {
-        //loop through original to also find new geometry
-        original.depthFirstTraversal(new SceneGraphVisitorAdapter() {
-            @Override
-            public void visit(Geometry geom) {
-                //will always return same class type as 2nd param, so casting is safe
-                Geometry spat = (Geometry) findTaggedSpatial(root, geom);
-                if (spat != null && spat.getMaterial() != null
-                        && geom.getMaterial() != null
-                        && !spat.getMaterial().equals(geom.getMaterial())) {
-                    spat.setMaterial(geom.getMaterial().clone());
-                    logger.log(LogLevel.USERINFO,
-                            "Updated material for Geometry {0}", geom.getName()
-                    );
+                String geomName = geom.getName();
+                if (geomName == null) {
+                    LOGGER.log(Level.WARNING, "Null Spatial name!");
+                    geomName = "null";
                 }
-            }
-        });
-    }
-
-    /**
-     * Adds a leaf to a spatial, including all nonexisting parents.
-     *
-     * @param root
-     * @param original
-     */
-    private static void addLeafWithNonExistingParents(Spatial root, Spatial leaf) {
-        if (!(root instanceof Node)) {
-            logger.log(Level.WARNING, "Cannot add new Leaf {0} to {1}, is not a Node!", new Object[]{leaf.getName(), root.getName()});
-            return;
-        }
-        for (Spatial s = leaf; s.getParent() != null; s = s.getParent()) {
-            Spatial parent = s.getParent();
-            Spatial other = findTaggedSpatial(root, parent);
-            if (other == null) {
-                continue;
-            }
-            if (other instanceof Node) {
-                logger.log(Level.INFO, "Attaching {0} to {1} in root {2} to add leaf {3}", new Object[]{s, other, root, leaf});
-                //set original path data to leaf and new parents
-                for (Spatial spt = leaf; spt != parent; spt = spt.getParent()) {
-                    if (spt == null) {
-                        // this is to avoid a crash when changing mesh names
-                        return;
-                    }
-                    spt.setUserData(ORIGINAL_NAME, spt.getName());
-                    spt.setUserData(ORIGINAL_PATH, getSpatialPath(spt));
-                    spt = spt.getParent();
+                geom.setUserData(SpatialUtil.ORIGINAL_NAME, geomName);
+                LOGGER.log(Level.FINE, "Set ORIGINAL_NAME for {0}",
+                        geomName);
+                final Spatial curSpat = geom;
+                String id = SpatialUtil.getSpatialPath(curSpat);
+                if (geomMap.contains(id)) {
+                    LOGGER.log(Level.WARNING, "Cannot create unique name "
+                            + "for Spatial {0}: {1}", new Object[]{geom, id});
                 }
-                //attach to new node in own root
-                Node otherNode = (Node) other;
-                otherNode.attachChild(s);
-                logger.log(LogLevel.USERINFO, "Attached Node {0} with leaf {0}", new Object[]{other.getName(), leaf.getName()});
-                return;
-            } else {
-                logger.log(Level.WARNING, "Cannot attach leaf {0} to found spatial {1} in root {2}, not a node.", new Object[]{leaf, other, root});
-            }
+                geomMap.add(id);
+                geom.setUserData(SpatialUtil.ORIGINAL_PATH, id);
+                LOGGER.log(Level.FINE, "Set ORIGINAL_PATH for {0}", id);
+            });
+        } else {
+            LOGGER.log(Level.SEVERE, "No Spatial available when trying to add"
+                    + " Spatial paths.");
         }
-        logger.log(Level.WARNING, "Could not attach new Leaf {0}, no root node found.", leaf.getName());
-    }
-
-    public static void updateAnimControlDataFromOriginal(final Spatial root, final Spatial original) {
-        //loop through original to also find new AnimControls, we expect all nodes etc. to exist
-        //TODO: can (blender) AnimControls end up in other nodes that are not a parent of the geometry they modify?
-        removeAnimData(root);
-        original.depthFirstTraversal(new SceneGraphVisitor() {
-            @Override
-            public void visit(Spatial spat) {
-                AnimControl animControl = spat.getControl(AnimControl.class);
-                if (animControl != null) {
-                    Spatial mySpatial = findTaggedSpatial(root, spat);
-                    if (mySpatial != null) {
-                        //TODO: move attachments: have to scan through all nodes and find the ones
-                        //where UserData "AttachedBone" == Bone and move it to new Bone
-                        AnimControl myAnimControl = mySpatial.getControl(AnimControl.class);
-                        SkeletonControl mySkeletonControl = spat.getControl(SkeletonControl.class);
-                        if (mySkeletonControl != null) {
-                            mySpatial.removeControl(mySkeletonControl);
-                        }
-                        if (myAnimControl != null) {
-                            mySpatial.removeControl(myAnimControl);
-                        }
-                        AnimControl newControl = (AnimControl) animControl.cloneForSpatial(mySpatial);
-                        if (mySpatial.getControl(SkeletonControl.class) == null) {
-                            logger.log(Level.INFO, "Adding control for {0}", mySpatial.getName());
-                            mySpatial.addControl(newControl);
-                        } else {
-                            logger.log(Level.INFO, "Control for {0} was added automatically", mySpatial.getName());
-                        }
-                        if (mySpatial.getControl(SkeletonControl.class) == null) {
-                            if (animControl.getSkeleton() == null) {
-                                logger.log(Level.INFO, "Could not add a SkeletonControl for {0}, because animControl.getSkeleton() return null. Broken file? Gltf?", mySpatial.getName());
-                            } else {
-                                mySpatial.addControl(new SkeletonControl(animControl.getSkeleton()));
-                            }
-                        } else {
-                            logger.log(Level.INFO, "SkeletonControl for {0} was added by AnimControl already", mySpatial.getName());
-                        }
-                        logger.log(LogLevel.USERINFO, "Updated animation for {0}", mySpatial.getName());
-                    } else {
-                        logger.log(Level.WARNING, "Could not find sibling for {0} in root {1} when trying to apply AnimControl data", new Object[]{spat, root});
-                    }
-                }
-            }
-        });
-        //TODO: remove old AnimControls?
     }
 
-    public static void removeAnimData(Spatial root) {
-        root.depthFirstTraversal(new SceneGraphVisitor() {
-            @Override
-            public void visit(Spatial spat) {
-                AnimControl animControl = spat.getControl(AnimControl.class);
-                if (animControl != null) {
-                    spat.removeControl(animControl);
-                    SkeletonControl skeletonControl = spat.getControl(SkeletonControl.class);
-                    if (skeletonControl != null) {
-                        spat.removeControl(skeletonControl);
-                    }
-                }
-            }
-        });
-    }
-
-    public static void clearRemovedOriginals(final Spatial root, final Spatial original) {
+    public static void clearRemovedOriginals(final Spatial root,
+                                             final Spatial original) {
         //TODO: Clear old stuff at all?
-        return;
     }
 
     /**
      * Finds out if a spatial has animations.
      *
-     * @param root
+     * @param root root spatial
      */
     public static boolean hasAnimations(final Spatial root) {
         final AtomicBoolean animFound = new AtomicBoolean(false);
-        root.depthFirstTraversal(new SceneGraphVisitor() {
-            public void visit(Spatial spatial) {
-                if (spatial.getControl(AnimControl.class) != null) {
-                    animFound.set(true);
-                }
+        root.depthFirstTraversal(spatial -> {
+            if (spatial.getControl(AnimComposer.class) != null) {
+                animFound.set(true);
             }
         });
         return animFound.get();
     }
 
-    private static class SpatialHolder {
-
-        Spatial spatial;
-    }
 }

+ 64 - 0
jme3-core/src/com/jme3/gde/core/util/TaggedSpatialFinder.java

@@ -0,0 +1,64 @@
+package com.jme3.gde.core.util;
+
+import com.jme3.scene.Spatial;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Finds a previously marked spatial in the supplied root Spatial, creates
+ * the name and path to be looked for from the given needle Spatial.
+ */
+public class TaggedSpatialFinder {
+    
+    private static final Logger LOGGER =
+            Logger.getLogger(TaggedSpatialFinder.class.getName());
+    
+    /**
+     * Finds a previously marked spatial in the supplied root Spatial, creates
+     * the name and path to be looked for from the given needle Spatial.
+     *
+     * @param root   supplied root Spatial
+     * @param needle spatial to look for
+     * @return found spatial
+     */
+    public Spatial find(final Spatial root, final Spatial needle) {
+        if (needle == null) {
+            LOGGER.log(Level.WARNING, "Trying to find null needle for {0}",
+                    root);
+            return null;
+        }
+        final String name = needle.getName();
+        final String path = SpatialUtil.getSpatialPath(needle);
+        if (name == null) {
+            LOGGER.log(Level.WARNING, "Trying to find tagged Spatial with "
+                    + "null name spatial for {0}.", root);
+            return null;
+        }
+        final Class<? extends Spatial> clazz = needle.getClass();
+        final String rootName = root.getUserData(SpatialUtil.ORIGINAL_NAME);
+        final String rootPath = root.getUserData(SpatialUtil.ORIGINAL_PATH);
+        if (name.equals(rootName) && path.equals(rootPath)) {
+            return root;
+        }
+        final SpatialHolder holder = new SpatialHolder();
+        root.depthFirstTraversal(spatial -> {
+            final String spatialName =
+                    spatial.getUserData(SpatialUtil.ORIGINAL_NAME);
+            final String spPath =
+                    spatial.getUserData(SpatialUtil.ORIGINAL_PATH);
+            if (name.equals(spatialName) && path.equals(spPath) && clazz.isInstance(spatial)) {
+                if (holder.spatial == null) {
+                    holder.spatial = spatial;
+                } else {
+                    LOGGER.log(Level.WARNING, "Found Spatial {0} twice in"
+                            + " {1}", new Object[]{path, root});
+                }
+            }
+        });
+        return holder.spatial;
+    }
+
+    private static class SpatialHolder {
+        private Spatial spatial;
+    }
+}

+ 121 - 0
jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyAnimationDataFromOriginal.java

@@ -0,0 +1,121 @@
+package com.jme3.gde.core.util.datatransfer;
+
+import com.jme3.anim.AnimClip;
+import com.jme3.anim.AnimComposer;
+import com.jme3.anim.Armature;
+import com.jme3.anim.SkinningControl;
+import com.jme3.gde.core.scene.ApplicationLogHandler;
+import com.jme3.gde.core.util.TaggedSpatialFinder;
+import com.jme3.scene.SceneGraphVisitor;
+import com.jme3.scene.Spatial;
+
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Copies AnimComposer and AnimClips from an updated spatial to the original.
+ */
+public final class CopyAnimationDataFromOriginal implements SpatialDataTransferInterface {
+
+    private static final Logger LOGGER
+            = Logger.getLogger(CopyAnimationDataFromOriginal.class.getName());
+
+    private final TaggedSpatialFinder finder;
+
+    public CopyAnimationDataFromOriginal(final TaggedSpatialFinder finder) {
+        this.finder = finder;
+    }
+
+    @Override
+    public void update(final Spatial root, final Spatial original) {
+        //loop through original to also find new AnimControls, we expect all 
+        // nodes etc. to exist
+        removeAnimData(root);
+        original.depthFirstTraversal(new SceneGraphVisitor() {
+
+            @Override
+            public void visit(final Spatial spatial) {
+                final AnimComposer animComposer
+                        = spatial.getControl(AnimComposer.class);
+                if (animComposer != null) {
+                    final Spatial mySpatial = finder.find(root, spatial);
+                    if (mySpatial != null) {
+                        //TODO: move attachments: have to scan through all
+                        // nodes and find the ones
+                        //where UserData "AttachedBone" == Bone and move it
+                        // to new Bone
+
+                        updateAndAddControl(mySpatial,
+                                animComposer,
+                                spatial.getControl(SkinningControl.class));
+
+                        LOGGER.log(ApplicationLogHandler.LogLevel.FINE,
+                                "Updated animation for {0}",
+                                mySpatial.getName());
+                    } else {
+                        LOGGER.log(Level.WARNING, "Could not find sibling for"
+                                + " {0} in root {1} when trying to apply "
+                                + "AnimControl data", new Object[]{spatial,
+                                root});
+                    }
+                }
+            }
+        });
+    }
+
+    private void updateAndAddControl(final Spatial spatial,
+                                     final AnimComposer originalAnimComposer,
+                                     final SkinningControl originalSkinningControl) {
+
+        final AnimComposer newControl = new AnimComposer();
+        copyAnimClips(newControl, originalAnimComposer);
+        if (spatial.getControl(AnimComposer.class) == null) {
+            LOGGER.log(Level.INFO, "Adding AnimComposer for {0}",
+                    spatial.getName());
+            spatial.addControl(newControl);
+        } else {
+            LOGGER.log(Level.INFO, "Control for {0} was added"
+                    + " automatically", spatial.getName());
+        }
+        if (spatial.getControl(SkinningControl.class) == null) {
+            if (originalSkinningControl == null) {
+                LOGGER.log(Level.INFO, "Could not add a SkinningControl. "
+                        + "Broken file?");
+            } else {
+                final SkinningControl skinningControl =
+                        new SkinningControl((Armature) originalSkinningControl.getArmature().jmeClone());
+                spatial.addControl(skinningControl);
+                LOGGER.log(Level.INFO, "Adding SkinningControl for {0}",
+                        spatial.getName());
+            }
+        }
+
+    }
+
+    private void copyAnimClips(final AnimComposer control,
+                               final AnimComposer original) {
+        final Collection<AnimClip> clips = original.getAnimClips();
+        for (final AnimClip c : clips) {
+            control.addAnimClip((AnimClip) c.jmeClone());
+            control.makeAction(c.getName());
+            LOGGER.log(Level.INFO, "Copied clip {0}",
+                    c.getName());
+        }
+    }
+
+    private void removeAnimData(final Spatial root) {
+        root.depthFirstTraversal(spatial -> {
+            final AnimComposer animControl = spatial.getControl(AnimComposer.class);
+            if (animControl != null) {
+                spatial.removeControl(animControl);
+            }
+            final SkinningControl skinningControl =
+                    spatial.getControl(SkinningControl.class);
+            if (skinningControl != null) {
+                spatial.removeControl(skinningControl);
+            }
+        });
+    }
+
+}

+ 42 - 0
jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyMaterialDataFromOriginal.java

@@ -0,0 +1,42 @@
+package com.jme3.gde.core.util.datatransfer;
+
+import com.jme3.gde.core.util.TaggedSpatialFinder;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.scene.Spatial;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Copies material data from an updated model to the original.
+ */
+public final class CopyMaterialDataFromOriginal implements SpatialDataTransferInterface {
+
+    private static final Logger LOGGER =
+            Logger.getLogger(CopyMaterialDataFromOriginal.class.getName());
+
+    private final TaggedSpatialFinder finder;
+
+    public CopyMaterialDataFromOriginal(final TaggedSpatialFinder finder) {
+        this.finder = finder;
+    }
+
+    @Override
+    public void update(final Spatial root, final Spatial original) {
+        //loop through original to also find new geometry
+        original.depthFirstTraversal(new SceneGraphVisitorAdapter() {
+            @Override
+            public void visit(Geometry geom) {
+                //will always return same class type as 2nd param, so casting
+                // is safe
+                final Geometry spat = (Geometry) finder.find(root, geom);
+                if (spat != null && spat.getMaterial() != null && geom.getMaterial() != null) {
+                    spat.setMaterial(geom.getMaterial());
+                    LOGGER.log(Level.FINE,
+                            "Updated material for Geometry {0}",
+                            geom.getName());
+                }
+            }
+        });
+    }
+}

+ 95 - 0
jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyMeshDataFromOriginal.java

@@ -0,0 +1,95 @@
+package com.jme3.gde.core.util.datatransfer;
+
+import com.jme3.gde.core.util.SpatialUtil;
+import com.jme3.gde.core.util.TaggedSpatialFinder;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.scene.Spatial;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Copies mesh data from an updated external file to the spatial.
+ */
+public class CopyMeshDataFromOriginal implements SpatialDataTransferInterface {
+
+    private static final Logger LOGGER =
+            Logger.getLogger(CopyMeshDataFromOriginal.class.getName());
+
+    private final TaggedSpatialFinder finder;
+
+    public CopyMeshDataFromOriginal(final TaggedSpatialFinder finder) {
+        this.finder = finder;
+    }
+
+    @Override
+    public void update(final Spatial root, final Spatial original) {
+        //loop through original to also find new geometry
+        original.depthFirstTraversal(new SceneGraphVisitorAdapter() {
+            @Override
+            public void visit(Geometry geom) {
+                //will always return same class type as 2nd param, so casting
+                // is safe
+                final Geometry spat = (Geometry) finder.find(root, geom);
+                if (spat != null) {
+                    spat.setMesh(geom.getMesh());
+                    LOGGER.log(Level.INFO, "Updated mesh for Geometry "
+                            + "{0}", geom.getName());
+                } else {
+                    addLeafWithNonExistingParents(root, geom);
+                }
+            }
+        });
+    }
+
+    /**
+     * Adds a leaf to a spatial, including all non-existing parents.
+     *
+     * @param root
+     * @param leaf
+     */
+    private void addLeafWithNonExistingParents(final Spatial root, 
+            final Spatial leaf) {
+        if (!(root instanceof Node)) {
+            LOGGER.log(Level.WARNING, "Cannot add new Leaf {0} to {1}, is not"
+                    + " a Node!", new Object[]{leaf.getName(), root.getName()});
+            return;
+        }
+        for (Spatial s = leaf; s.getParent() != null; s = s.getParent()) {
+            final Spatial parent = s.getParent();
+            final Spatial other = finder.find(root, parent);
+            if (other == null) {
+                continue;
+            }
+            if (other instanceof Node) {
+                LOGGER.log(Level.INFO, "Attaching {0} to {1} in root {2} to "
+                        + "add leaf {3}", new Object[]{s, other, root, leaf});
+                //set original path data to leaf and new parents
+                for (Spatial spt = leaf; spt != parent; spt = spt.getParent()) {
+                    // this is to avoid a crash when changing
+                    // names of meshes externally
+                    if (spt == null) {
+                        return;
+                    }
+                    spt.setUserData(SpatialUtil.ORIGINAL_NAME, spt.getName());
+                    spt.setUserData(SpatialUtil.ORIGINAL_PATH,
+                            SpatialUtil.getSpatialPath(spt));
+                    spt = spt.getParent();
+                }
+                //attach to new node in own root
+                Node otherNode = (Node) other;
+                otherNode.attachChild(s);
+                LOGGER.log(Level.INFO, "Attached Node {0} with leaf "
+                        + "{0}", new Object[]{other.getName(), leaf.getName()});
+                return;
+            } else {
+                LOGGER.log(Level.WARNING, "Cannot attach leaf {0} to found "
+                                + "spatial {1} in root {2}, not a node.",
+                        new Object[]{leaf, other, root});
+            }
+        }
+        LOGGER.log(Level.WARNING, "Could not attach new Leaf {0}, no root "
+                + "node found.", leaf.getName());
+    }
+}

+ 54 - 0
jme3-core/src/com/jme3/gde/core/util/datatransfer/CopyTransformDataFromOriginal.java

@@ -0,0 +1,54 @@
+package com.jme3.gde.core.util.datatransfer;
+
+import com.jme3.gde.core.util.TaggedSpatialFinder;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.scene.Spatial;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Copies Transform data (translation, rotation, scale) from an updated spatial
+ * to the original.
+ */
+public final class CopyTransformDataFromOriginal implements 
+        SpatialDataTransferInterface {
+
+    private static final Logger LOGGER
+            = Logger.getLogger(CopyAnimationDataFromOriginal.class.getName());
+
+    private final TaggedSpatialFinder finder;
+
+    public CopyTransformDataFromOriginal(final TaggedSpatialFinder finder) {
+        this.finder = finder;
+    }
+
+    @Override
+    public void update(final Spatial root, final Spatial original) {
+        original.depthFirstTraversal(new SceneGraphVisitorAdapter() {
+
+            @Override
+            public void visit(final Geometry geom) {
+                final Geometry spat = (Geometry) finder.find(root, geom);
+                if (spat != null) {
+                    spat.setLocalTransform(geom.getLocalTransform());
+                    LOGGER.log(Level.FINE,
+                            "Updated transform for Geometry {0}",
+                            geom.getName());
+                }
+            }
+
+            @Override
+            public void visit(final Node node) {
+                final Node spat = (Node) finder.find(root, node);
+                if (spat != null) {
+                    spat.setLocalTransform(node.getLocalTransform());
+                    LOGGER.log(Level.FINE,
+                            "Updated transform for Node {0}", node.getName());
+                }
+            }
+        });
+    }
+
+}

+ 19 - 0
jme3-core/src/com/jme3/gde/core/util/datatransfer/SpatialDataTransferInterface.java

@@ -0,0 +1,19 @@
+package com.jme3.gde.core.util.datatransfer;
+
+import com.jme3.scene.Spatial;
+
+/**
+ * Generic interface for data transfer from files updated externally
+ *
+ * @author rickard
+ */
+public interface SpatialDataTransferInterface {
+
+    /**
+     * Performs the data transfer.
+     *
+     * @param root     the node containing the spatial to update
+     * @param original spatial to update data from
+     */
+    abstract void update(Spatial root, Spatial original);
+}

+ 4 - 10
jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java

@@ -43,7 +43,6 @@ import com.jme3.gde.materialdefinition.fileStructure.leaves.LeafStatement;
 import com.jme3.gde.materialdefinition.fileStructure.leaves.MatParamBlock;
 import com.jme3.gde.materialdefinition.fileStructure.leaves.OutputMappingBlock;
 import com.jme3.gde.materialdefinition.navigator.node.MatDefNode;
-import com.jme3.material.MatParam;
 import com.jme3.material.Material;
 import com.jme3.material.MaterialDef;
 import com.jme3.material.TechniqueDef;
@@ -78,7 +77,9 @@ import org.openide.util.Lookup;
 import org.openide.util.WeakListeners;
 
 /**
- *
+ * This is the MatDef representation in the editor. It will update the file with
+ * any changes.
+ * 
  * @author Nehon
  */
 public class EditableMatDefFile {
@@ -254,14 +255,6 @@ public class EditableMatDefFile {
         try {
             //material.selectTechnique("Default", SceneApplication.getApplication().getRenderManager());
             if (matToRemove != null) {
-                for (MatParam matParam : matToRemove.getParams()) {
-                    try {
-                        material.setParam(matParam.getName(), matParam.getVarType(), matParam.getValue());
-                    } catch (IllegalArgumentException ie) {
-                        matToRemove.clearParam(matParam.getName());
-                    }
-
-                }
                 obj.getLookupContents().remove(matToRemove);
                 matToRemove = null;
             }
@@ -405,4 +398,5 @@ public class EditableMatDefFile {
         setCurrentTechnique(null);
         setLoaded(false);
     }
+    
 }

+ 9 - 0
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/BackdropPanel.java

@@ -528,5 +528,14 @@ public class BackdropPanel extends JPanel implements MouseListener, ChangeListen
             ((MouseMotionListener) c).mouseMoved(SwingUtilities.convertMouseEvent(this, e, c));
         }
     }
+    
+    public MaterialPreviewRenderer getRenderer(){
+        return renderer;
+    }
 
+    public void refreshOnly() {
+        if (mat != null) {
+            renderer.refreshOnly();
+        }
+    }
 }

+ 3 - 0
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/Bundle.properties

@@ -19,3 +19,6 @@ ShaderNodeToolBar.codeButton.toolTipText=Display code
 NodeToolBar.deleteButton.toolTipText=Delete node
 NodeToolBar.deleteButton.text=
 NodeToolBar.codeButton.toolTipText=Display code
+MatDefEditorToolBar.toggleLiveUpdates.actionCommand=toggleLiveUpdates
+MatDefEditorToolBar.toggleLiveUpdates.toolTipText=Toggle Live Backpanel Updates
+MatDefEditorToolBar.toggleLiveUpdates.text=

+ 39 - 1
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.form

@@ -31,7 +31,9 @@
               <Component id="jButton1" min="-2" pref="53" max="-2" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
               <Component id="jButton2" min="-2" max="-2" attributes="0"/>
-              <EmptySpace min="0" pref="103" max="32767" attributes="0"/>
+              <EmptySpace max="32767" attributes="0"/>
+              <Component id="jToggleButton2" min="-2" max="-2" attributes="0"/>
+              <EmptySpace min="-2" pref="99" max="-2" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -42,6 +44,7 @@
               <Component id="techniqueComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
               <Component id="jButton1" alignment="3" min="-2" max="-2" attributes="0"/>
               <Component id="jButton2" alignment="3" min="-2" max="-2" attributes="0"/>
+              <Component id="jToggleButton2" alignment="3" min="-2" max="-2" attributes="0"/>
           </Group>
           <Component id="jSeparator1" alignment="0" max="32767" attributes="0"/>
       </Group>
@@ -106,5 +109,40 @@
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButton2ActionPerformed"/>
       </Events>
     </Component>
+    <Component class="javax.swing.JToggleButton" name="jToggleButton2">
+      <Properties>
+        <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
+          <Image iconType="3" name="/com/jme3/gde/core/editor/icons/repeat.png"/>
+        </Property>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/materialdefinition/editor/Bundle.properties" key="MatDefEditorToolBar.toggleLiveUpdates.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/materialdefinition/editor/Bundle.properties" key="MatDefEditorToolBar.toggleLiveUpdates.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="actionCommand" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="com/jme3/gde/materialdefinition/editor/Bundle.properties" key="MatDefEditorToolBar.toggleLiveUpdates.actionCommand" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="horizontalAlignment" type="int" value="11"/>
+        <Property name="horizontalTextPosition" type="int" value="0"/>
+        <Property name="iconTextGap" type="int" value="0"/>
+        <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
+          <Insets value="[2, 14, 2, 18]"/>
+        </Property>
+        <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[25, 24]"/>
+        </Property>
+        <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[25, 24]"/>
+        </Property>
+        <Property name="name" type="java.lang.String" value="toggleLiveUpdates" noResource="true"/>
+        <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+          <Dimension value="[25, 24]"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jToggleButton2ActionPerformed"/>
+      </Events>
+    </Component>
   </SubComponents>
 </Form>

+ 37 - 2
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java

@@ -42,6 +42,7 @@ import javax.swing.JLabel;
 import javax.swing.JList;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JToggleButton;
 import javax.swing.ListCellRenderer;
 
 /**
@@ -101,6 +102,7 @@ public class MatDefEditorToolBar extends JPanel {
         jButton1 = new javax.swing.JButton();
         jSeparator1 = new javax.swing.JSeparator();
         jButton2 = new javax.swing.JButton();
+        jToggleButton2 = new javax.swing.JToggleButton();
 
         setPreferredSize(new java.awt.Dimension(474, 20));
 
@@ -133,6 +135,24 @@ public class MatDefEditorToolBar extends JPanel {
             }
         });
 
+        jToggleButton2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/core/editor/icons/repeat.png"))); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(jToggleButton2, org.openide.util.NbBundle.getMessage(MatDefEditorToolBar.class, "MatDefEditorToolBar.toggleLiveUpdates.text")); // NOI18N
+        jToggleButton2.setToolTipText(org.openide.util.NbBundle.getMessage(MatDefEditorToolBar.class, "MatDefEditorToolBar.toggleLiveUpdates.toolTipText")); // NOI18N
+        jToggleButton2.setActionCommand(org.openide.util.NbBundle.getMessage(MatDefEditorToolBar.class, "MatDefEditorToolBar.toggleLiveUpdates.actionCommand")); // NOI18N
+        jToggleButton2.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
+        jToggleButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
+        jToggleButton2.setIconTextGap(0);
+        jToggleButton2.setMargin(new java.awt.Insets(2, 14, 2, 18));
+        jToggleButton2.setMaximumSize(new java.awt.Dimension(25, 24));
+        jToggleButton2.setMinimumSize(new java.awt.Dimension(25, 24));
+        jToggleButton2.setName("toggleLiveUpdates"); // NOI18N
+        jToggleButton2.setPreferredSize(new java.awt.Dimension(25, 24));
+        jToggleButton2.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                jToggleButton2ActionPerformed(evt);
+            }
+        });
+
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
         this.setLayout(layout);
         layout.setHorizontalGroup(
@@ -147,7 +167,9 @@ public class MatDefEditorToolBar extends JPanel {
                 .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addComponent(jButton2)
-                .addGap(0, 103, Short.MAX_VALUE))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                .addComponent(jToggleButton2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addGap(99, 99, 99))
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -155,7 +177,8 @@ public class MatDefEditorToolBar extends JPanel {
                 .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                 .addComponent(techniqueComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                 .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addComponent(jButton2))
+                .addComponent(jButton2)
+                .addComponent(jToggleButton2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
             .addComponent(jSeparator1)
         );
     }// </editor-fold>//GEN-END:initComponents
@@ -187,16 +210,28 @@ public class MatDefEditorToolBar extends JPanel {
         }
     }//GEN-LAST:event_jButton1ActionPerformed
 
+    /**
+     * Autolayout button
+     * @param evt 
+     */
     private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
        parent.getDiagram().autoLayout();
     }//GEN-LAST:event_jButton2ActionPerformed
 
+    /**
+     * Toggle continuous updates thread 
+     * @param evt 
+     */
+    private void jToggleButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleButton2ActionPerformed
+        parent.getDiagram().toggleUpdateThread(((JToggleButton)evt.getSource()).isSelected());
+    }//GEN-LAST:event_jToggleButton2ActionPerformed
 
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JButton jButton1;
     private javax.swing.JButton jButton2;
     private javax.swing.JLabel jLabel1;
     private javax.swing.JSeparator jSeparator1;
+    private javax.swing.JToggleButton jToggleButton2;
     private javax.swing.JComboBox<TechniqueBlock> techniqueComboBox;
     // End of variables declaration//GEN-END:variables
 }

+ 7 - 10
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorlElement.java

@@ -39,6 +39,7 @@ import com.jme3.gde.core.editor.nodes.NodePanel;
 import com.jme3.gde.core.editor.nodes.Selectable;
 import com.jme3.asset.ShaderNodeDefinitionKey;
 import com.jme3.gde.core.assets.ProjectAssetManager;
+import com.jme3.gde.core.errorreport.ExceptionPanel;
 import com.jme3.gde.materialdefinition.EditableMatDefFile;
 import com.jme3.gde.materialdefinition.MatDefDataObject;
 import com.jme3.gde.materialdefinition.MatDefMetaData;
@@ -81,6 +82,8 @@ import org.netbeans.core.spi.multiview.CloseOperationState;
 import org.netbeans.core.spi.multiview.MultiViewElement;
 import org.netbeans.core.spi.multiview.MultiViewElementCallback;
 import org.netbeans.core.spi.multiview.text.MultiViewEditorElement;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
 import org.openide.awt.UndoRedo;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
@@ -133,7 +136,6 @@ public final class MatDefEditorlElement extends JPanel implements
         if (!file.isLoaded()) {
             throw new IllegalArgumentException("Cannot build MatDefEditorlElement: Failed at loading the EditableMatDefFile");
         }
-        
         reload(file, lkp);        
         toolbar.setParent(this);
         toolbar.addTechnique(lkp.lookup(MatDefBlock.class).getTechniques());
@@ -769,15 +771,10 @@ public final class MatDefEditorlElement extends JPanel implements
             }
         } else {
             diagram1.clear();
-            JLabel error = new JLabel("<html><center>Cannot load material definition.<br>Please see the error log and fix it in the text editor</center></html>");
-            error.setForeground(Color.ORANGE);
-            error.setFont(new Font("Arial", Font.BOLD, 24));
-            error.setBounds(0, 0, 400, 100);
-            jScrollPane1.getHorizontalScrollBar().setValue(0);
-            error.setLocation(jScrollPane1.getViewport().getWidth() / 2 - 200, jScrollPane1.getViewport().getHeight() / 2 - 50);
-            diagram1.add(error);
-            diagram1.repaint();
-
+            ExceptionPanel ep = new ExceptionPanel("Please see the error log and fix it in the text editor", false);
+            DialogDescriptor d = new DialogDescriptor(ep, "Cannot load material definition", true, new Object[] { DialogDescriptor.OK_OPTION }, DialogDescriptor.DEFAULT_OPTION, DialogDescriptor.DEFAULT_ALIGN, null, null);
+            DialogDisplayer.getDefault().notifyLater(d);
+            
         }
     }
 }

+ 57 - 4
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/ShaderNodeDiagram.java

@@ -44,10 +44,10 @@ import com.jme3.gde.materialdefinition.editor.ShaderNodePanel.NodeType;
 import com.jme3.gde.materialdefinition.fileStructure.ShaderNodeBlock;
 import com.jme3.gde.materialdefinition.fileStructure.leaves.MappingBlock;
 import com.jme3.gde.core.editor.icons.Icons;
+import com.jme3.gde.core.errorreport.ExceptionUtils;
 import com.jme3.gde.materialdefinition.utils.MaterialUtils;
 import com.jme3.material.Material;
 import com.jme3.shader.Shader;
-//import static com.jme3.gde.materialdefinition.editor.ShaderNodePanel.NodeType;
 import com.jme3.shader.ShaderNodeDefinition;
 import com.jme3.shader.ShaderNodeVariable;
 import com.jme3.shader.UniformBinding;
@@ -58,9 +58,10 @@ import java.awt.event.ActionListener;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
 import java.awt.event.MouseEvent;
-import java.awt.event.MouseMotionListener;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.JMenuItem;
 import javax.swing.JViewport;
 import javax.swing.SwingUtilities;
@@ -68,6 +69,7 @@ import javax.swing.SwingUtilities;
 /**
  * The Diagram is the main canvas where all nodes {@link DraggablePanel} and
  * their connections {@link ConnectionEndpoint} {@link Connection} are added onto.
+ * 
  * @author Nehon
  */
 public class ShaderNodeDiagram extends Diagram implements ComponentListener {
@@ -75,7 +77,8 @@ public class ShaderNodeDiagram extends Diagram implements ComponentListener {
     protected List<ShaderOutBusPanel> outBuses = new ArrayList<ShaderOutBusPanel>();
     private String currentTechniqueName;
     private final BackdropPanel backDrop = new BackdropPanel();
-    private final Point pp = new Point();
+    private Thread backgroundThread;
+    private UpdateBackgroundRunnable backgroundUpdate = new UpdateBackgroundRunnable();
 
     @SuppressWarnings("LeakingThisInConstructor")
     public ShaderNodeDiagram() {
@@ -107,7 +110,6 @@ public class ShaderNodeDiagram extends Diagram implements ComponentListener {
                     return;
                 }
             }
-
             dispatchToOutBuses(e);
         } else {
             super.mouseReleased(e); // Handle all the UI Stuff
@@ -320,6 +322,7 @@ public class ShaderNodeDiagram extends Diagram implements ComponentListener {
     public void clear() {
         super.clear();
         outBuses.clear();
+        backgroundUpdate.setRunning(false);
     }
 
     @Override
@@ -489,4 +492,54 @@ public class ShaderNodeDiagram extends Diagram implements ComponentListener {
         return 0;
 
     }
+
+    @Override
+    public void toggleUpdateThread(boolean on) {
+        if (on && !backgroundUpdate.isRunning()) {
+            backgroundUpdate.setRunning(true);
+            backgroundThread = new Thread(backgroundUpdate);
+            backgroundThread.setDaemon(true);
+            backgroundThread.start();
+        } else if (!on && backgroundUpdate.isRunning()) {
+            try {
+                backgroundUpdate.setRunning(false);
+                backgroundThread.join();
+            } catch (InterruptedException ex) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    private final class UpdateBackgroundRunnable implements Runnable {
+
+        private boolean running;
+
+        @Override
+        public void run() {
+            while (running) {
+                if (backDrop.isVisible() && !backDrop.getRenderer().isPreviewRequested()) {
+                    backDrop.refreshOnly();
+                }
+                try {
+                    Thread.sleep(20);
+                } catch (InterruptedException ex) {
+                    running = false;
+                    ExceptionUtils.caughtException(ex, "Material update thread caught an exception and shut down.");
+                }
+                if (!ShaderNodeDiagram.this.isShowing()) {
+                    running = false;
+                }
+            }
+            Logger.getLogger(ShaderNodeDiagram.class.getName()).log(Level.INFO, "UpdateThread stopped");
+        }
+
+        public boolean isRunning() {
+            return running;
+        }
+
+        public void setRunning(boolean on) {
+            this.running = on;
+        }
+    }
+    
 }

+ 3 - 3
jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/ShaderNodePanel.java

@@ -109,9 +109,9 @@ public class ShaderNodePanel extends NodePanel implements InOut,
     public ShaderNodePanel(ShaderNodeVariable singleOut, NodeType type) {
         super();
         this.type = type;
-        List<ShaderNodeVariable> outputs = new ArrayList<ShaderNodeVariable>();
+        List<ShaderNodeVariable> outputs = new ArrayList<>();
         outputs.add(singleOut);
-        init(new ArrayList<ShaderNodeVariable>(), outputs);
+        init(new ArrayList<>(), outputs);
         setToolbar(new ShaderNodeToolBar(this));
     }
     
@@ -194,7 +194,7 @@ public class ShaderNodePanel extends NodePanel implements InOut,
                 break;
             case Attribute:
                 header.setIcon(Icons.attrib);
-                setNameAndTitle("Attribute"); // sets text _and_ tooltip the same
+                setNameAndTitle("Attr");
                 break;
             case WorldParam:
                 header.setIcon(Icons.world);

+ 36 - 0
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/DefinesBlock.java

@@ -0,0 +1,36 @@
+package com.jme3.gde.materialdefinition.fileStructure;
+
+import com.jme3.gde.materialdefinition.fileStructure.leaves.DefineBlock;
+import com.jme3.util.blockparser.Statement;
+import java.util.List;
+
+/**
+ * Handles the surrounding statement of Defines{} in a MaterialDefinition
+ * Each row is a DefineBlock
+ * @author rickard
+ */
+public final class DefinesBlock extends UberStatement {
+
+    protected DefinesBlock(int lineNumber, String line) {
+        super(lineNumber, line);
+    }
+
+    public DefinesBlock(Statement sta) {
+        this(sta.getLineNumber(), sta.getLine());
+        for (Statement statement : sta.getContents()) {
+            addStatement(new DefineBlock(statement));
+        }
+    }
+
+    public List<DefineBlock> getDefineBlocks() {
+        return getBlocks(DefineBlock.class);
+    }
+
+    public void addDefineBlock(DefineBlock block) {
+        contents.add(block);
+    }
+
+    public void removeDefineBlock(DefineBlock block) {
+        contents.remove(block);
+    }
+}

+ 0 - 3
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/InputMappingsBlock.java

@@ -4,10 +4,7 @@
  */
 package com.jme3.gde.materialdefinition.fileStructure;
 
-import com.jme3.gde.materialdefinition.fileStructure.*;
 import com.jme3.gde.materialdefinition.fileStructure.leaves.InputMappingBlock;
-import com.jme3.gde.materialdefinition.fileStructure.leaves.MatParamBlock;
-import com.jme3.gde.materialdefinition.fileStructure.leaves.OutputMappingBlock;
 import com.jme3.util.blockparser.Statement;
 import java.util.List;
 

+ 4 - 15
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/ShaderNodeBlock.java

@@ -16,16 +16,18 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- *
+ * A ShaderNodeBlock is a shader node in the MatDef file.
+ * 
  * @author Nehon
  */
-public class ShaderNodeBlock extends UberStatement implements Comparable<ShaderNodeBlock>, PropertyChangeListener {
+public class ShaderNodeBlock extends UberStatement implements PropertyChangeListener {
 
     public final static String POSITION = "position";
     public final static String INPUT = "input";
     public final static String OUTPUT = "output";
     public static final String ADD_MAPPING = "addMapping";
     public static final String REMOVE_MAPPING = "removeMapping";
+    public static final String VALUE = "defaultValue";
     protected String name;
     //built up data for fast sorting 
     protected int spatialOrder;
@@ -192,19 +194,6 @@ public class ShaderNodeBlock extends UberStatement implements Comparable<ShaderN
         fire(REMOVE_MAPPING, mapping, null);
     }
 
-    public int compareTo(ShaderNodeBlock o) {
-        if (inputNodes.contains(o.getName())) {
-            return 1;
-        }
-        if (o.inputNodes.contains(name)) {
-            return -1;
-        }
-        if ((globalInput && o.globalOutput) || (o.globalInput && globalOutput)) {
-            return (int) Math.signum(spatialOrder - o.spatialOrder);
-        }
-        return 0;
-    }
-
     public void propertyChange(PropertyChangeEvent evt) {
         if (evt.getPropertyName().equals(POSITION)) {
             spatialOrder = (Integer) evt.getNewValue();

+ 80 - 13
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/ShaderNodesBlock.java

@@ -8,19 +8,22 @@ import com.jme3.util.blockparser.Statement;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import org.openide.util.WeakListeners;
 
 /**
- *
+ * A ShaderNodesBlock is a set of shader nodes in the MatDef file, for example all frag shader nodes.
+ * 
  * @author Nehon
  */
 public class ShaderNodesBlock extends UberStatement implements PropertyChangeListener {
-    
+        
     protected ShaderNodesBlock(int lineNumber, String line) {
         super(lineNumber, line);
+        
     }
-    
+
     public ShaderNodesBlock(Statement sta) {
         this(sta.getLineNumber(), sta.getLine());
         for (Statement statement : sta.getContents()) {
@@ -29,31 +32,95 @@ public class ShaderNodesBlock extends UberStatement implements PropertyChangeLis
             addStatement(b);
         }
     }
-    
-    public List<ShaderNodeBlock> getShaderNodes() {        
+
+    public List<ShaderNodeBlock> getShaderNodes() {
         return getBlocks(ShaderNodeBlock.class);
     }
-    
+
     public void addShaderNode(ShaderNodeBlock shaderNodeBlock) {
-        addStatement(shaderNodeBlock);      
+        addStatement(shaderNodeBlock);
         shaderNodeBlock.addPropertyChangeListener(WeakListeners.propertyChange(this, shaderNodeBlock));
     }
-    
+
     public boolean removeShaderNode(ShaderNodeBlock shaderNodeBlock) {
         return contents.remove(shaderNodeBlock);
     }
-    
+
+    @Override
     public void propertyChange(PropertyChangeEvent evt) {
-        if (evt.getPropertyName().equals("order")) {
+        if (evt.getPropertyName().equals(ShaderNodeBlock.ADD_MAPPING) || evt.getPropertyName().equals("order")) {
             sort();
-        }        
+        }
     }
-    
+
     public void sort() {
         List<ShaderNodeBlock> list = getShaderNodes();
-        Collections.sort(list);        
+        int passes = 0;
+        NodeComparator nodeComparator = new NodeComparator(list);
+        while (nodeComparator.changes != 0 && passes < 10) {
+            nodeComparator.changes = 0;
+            Collections.sort(list, nodeComparator);
+            passes++;
+        }
         contents.clear();
         contents.addAll(list);
         fire("reorder", null, null);
     }
+
+    /**
+     * Sorts nodes so that they are initialized after their input dependencies.
+     * Will look higher up in hierarchy and move node incrementally. This may require several passes
+     */
+    private final class NodeComparator implements Comparator<ShaderNodeBlock> {
+
+        private final List<ShaderNodeBlock> list;
+        private int changes = -1;
+
+        public NodeComparator(List<ShaderNodeBlock> list) {
+            this.list = list;
+        }
+
+        @Override
+        public int compare(ShaderNodeBlock o1, ShaderNodeBlock o2) {
+            if (o1.name.equals(o2.name)) {
+                return 0;
+            }
+            if (hasInputConnection(o2.name, o1)) {
+                changes++;
+                return 1;
+            }
+            if (hasInputConnection(o1.name, o2)) {
+                changes++;
+                return -1;
+            }
+
+            if ((o1.globalInput && o2.globalOutput) || (o2.globalInput && o1.globalOutput)) {
+                changes++;
+                return (int) Math.signum(o1.spatialOrder - o2.spatialOrder);
+            }
+            return 0;
+        }
+
+        private ShaderNodeBlock findAncestorNode(String name) {
+            for (ShaderNodeBlock node : list) {
+                if (node.name.equals(name)) {
+                    return node;
+                }
+            }
+            return null;
+        }
+
+        private boolean hasInputConnection(String name, ShaderNodeBlock node) {
+            for (String s : node.inputNodes) {
+                if (s.equals(name)) {
+                    return true;
+                }
+                ShaderNodeBlock ancestor = findAncestorNode(s);
+                if (ancestor != null && hasInputConnection(name, ancestor)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
 }

+ 4 - 2
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java

@@ -52,6 +52,8 @@ public class TechniqueBlock extends UberStatement {
                 addStatement(new VertexShaderNodesBlock(statement));
             } else if (statement.getLine().trim().startsWith("FragmentShaderNodes")) {
                 addStatement(new FragmentShaderNodesBlock(statement));
+            } else if (statement.getLine().trim().startsWith("Defines")) {
+                addStatement(new DefinesBlock(statement));
             } else {
                 addStatement(new UnsupportedStatement(statement));
             }
@@ -111,7 +113,7 @@ public class TechniqueBlock extends UberStatement {
             return getWorldParameters().getWorldParams();
         else {
             logger.log(Level.WARNING, "Unable to build ShaderNodes: Could not find any WorldParameters. Most likely the technique {0} is broken.", line);
-            return new ArrayList<WorldParamBlock>();
+            return new ArrayList<>();
         }
     }
 
@@ -189,7 +191,7 @@ public class TechniqueBlock extends UberStatement {
     }
 
     public List<ShaderNodeBlock> getShaderNodes() {
-        List<ShaderNodeBlock> list = new ArrayList<ShaderNodeBlock>();
+        List<ShaderNodeBlock> list = new ArrayList<>();
         
         VertexShaderNodesBlock vert_block = getBlock(VertexShaderNodesBlock.class);
         if (vert_block == null)

+ 2 - 1
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/UberStatement.java

@@ -13,7 +13,8 @@ import java.util.LinkedList;
 import java.util.List;
 
 /**
- *
+ * A statement that will be enclosed with curly braces.
+ * 
  * @author Nehon
  */
 @SuppressWarnings({"unchecked", "rawtypes"})

+ 76 - 0
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/leaves/DefineBlock.java

@@ -0,0 +1,76 @@
+package com.jme3.gde.materialdefinition.fileStructure.leaves;
+
+import com.jme3.util.blockparser.Statement;
+import java.util.Objects;
+
+/**
+ * Handles one row of Defines in a MaterialDef, ie ALBEDOMAP : AlbedoMap
+ * @author rickard
+ */
+public final class DefineBlock extends LeafStatement {
+
+    private String name;
+    private String define;
+
+    protected DefineBlock(int lineNumber, String line) {
+        super(lineNumber, line);
+    }
+
+    public DefineBlock(Statement sta) {
+        this(sta.getLineNumber(), sta.getLine());
+        parse(sta);
+        updateLine();
+    }
+
+    public DefineBlock(String name, String define) {
+        super(0, "");
+        this.name = name;
+        this.define = define;
+        updateLine();
+    }
+
+    private void updateLine() {
+        this.line = String.format("%s : %s", name, define);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+        updateLine();
+    }
+
+    public String getDefine() {
+        return define;
+    }
+
+    public void setDefine(String define) {
+        this.define = define;
+        updateLine();
+    }
+
+    private void parse(Statement sta) {
+        String[] values = sta.getLine().split(":");
+        name = values[0].trim();
+        define = values[1].trim();
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 67 * hash + Objects.hashCode(this.name);
+        hash = 67 * hash + Objects.hashCode(this.define);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DefineBlock)) {
+            return false;
+        }
+        return ((DefineBlock) obj).getName().equals(name) && ((DefineBlock) obj).getDefine().equals(define);
+    }
+
+}

+ 5 - 4
jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/leaves/LeafStatement.java

@@ -12,13 +12,14 @@ import java.util.LinkedList;
 import java.util.List;
 
 /**
- *
+ * A statement that will not be decorated.
+ * 
  * @author Nehon
  */
 @SuppressWarnings({"unchecked", "rawtypes"})
 public class LeafStatement extends Statement {
 
-    private List listeners = Collections.synchronizedList(new LinkedList());
+    private final List listeners = Collections.synchronizedList(new LinkedList());
 
     public void addPropertyChangeListener(PropertyChangeListener pcl) {
         listeners.add(pcl);
@@ -31,8 +32,8 @@ public class LeafStatement extends Statement {
     protected void fire(String propertyName, Object old, Object nue) {
         //Passing 0 below on purpose, so you only synchronize for one atomic call:
         PropertyChangeListener[] pcls = (PropertyChangeListener[]) listeners.toArray(new PropertyChangeListener[0]);
-        for (int i = 0; i < pcls.length; i++) {
-            pcls[i].propertyChange(new PropertyChangeEvent(this, propertyName, old, nue));
+        for (PropertyChangeListener pcl : pcls) {
+            pcl.propertyChange(new PropertyChangeEvent(this, propertyName, old, nue));
         }
     }
 

+ 26 - 5
jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java

@@ -31,7 +31,8 @@ import javax.swing.ImageIcon;
 import javax.swing.JLabel;
 
 /**
- *
+ * Handles rendering of materials in preview widgets of Material and Shader Node editor.
+ * 
  * @author Nehon
  */
 public class MaterialPreviewRenderer implements SceneListener {
@@ -42,8 +43,9 @@ public class MaterialPreviewRenderer implements SceneListener {
     private Geometry currentGeom;
     private Material currentMaterial;
     private boolean init = false;
-    private final JLabel label;
+    final JLabel label;
     private final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5);
+    private boolean previewRequested;
 
     public enum DisplayType {
 
@@ -58,9 +60,9 @@ public class MaterialPreviewRenderer implements SceneListener {
 
     private void init() {
         SceneApplication.getApplication().addSceneListener(this);
-        Sphere sphMesh = new Sphere(32, 32, 2.5f);
-        sphMesh.setTextureMode(Sphere.TextureMode.Projected);
-        sphMesh.updateGeometry(32, 32, 2.5f, false, false);
+        Sphere sphMesh = new Sphere(64, 64, 2.5f);
+        sphMesh.setTextureMode(Sphere.TextureMode.Polar);
+        sphMesh.updateGeometry(64, 64, 2.5f, false, false);
         Logger log = Logger.getLogger(TangentBinormalGenerator.class.getName());
         log.setLevel(Level.SEVERE);
         TangentBinormalGenerator.generate(sphMesh);
@@ -246,6 +248,7 @@ public class MaterialPreviewRenderer implements SceneListener {
                     label.setIcon(icon);
                 }
             });
+            previewRequested = false;
         }
     }
 
@@ -253,4 +256,22 @@ public class MaterialPreviewRenderer implements SceneListener {
         SceneApplication.getApplication().removeSceneListener(this);
         exec.shutdownNow();
     }
+    
+    public boolean isPreviewRequested(){
+        return previewRequested;
+    }
+    
+    public void refreshOnly(){
+        previewRequested = true;
+        SceneApplication.getApplication().enqueue((Callable<Object>) () -> {
+            if (currentGeom.getMaterial() != null) {
+                PreviewRequest request = new PreviewRequest(MaterialPreviewRenderer.this, currentGeom, label.getWidth(), label.getHeight());
+                request.getCameraRequest().setLocation(new Vector3f(0, 0, 7));
+                request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
+                SceneApplication.getApplication().createPreview(request);
+            }
+            return null;
+        });
+    }
+    
 }

+ 29 - 3
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/SNDefDataObject.java

@@ -1,7 +1,33 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2022 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.shadernodedefinition;
 

+ 29 - 3
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/SNDefVisualElement.java

@@ -1,7 +1,33 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2022 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.shadernodedefinition;
 

+ 29 - 3
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefVisualPanel1.java

@@ -1,7 +1,33 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2022 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.shadernodedefinition.wizard;
 

+ 29 - 3
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefVisualPanel2.java

@@ -1,7 +1,33 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2022 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.shadernodedefinition.wizard;
 

+ 29 - 3
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardIterator.java

@@ -1,7 +1,33 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2022 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.shadernodedefinition.wizard;
 

+ 41 - 4
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardPanel1.java

@@ -1,15 +1,44 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2022 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.shadernodedefinition.wizard;
 
+import java.util.regex.Pattern;
 import javax.swing.event.ChangeListener;
+
 import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
 import org.openide.util.HelpCtx;
 
-public class SNDefWizardPanel1 implements WizardDescriptor.Panel<WizardDescriptor> {
+public class SNDefWizardPanel1 implements WizardDescriptor.ValidatingPanel<WizardDescriptor> {
 
     /**
      * The visual component that displays this panel. If you need to access the
@@ -17,6 +46,8 @@ public class SNDefWizardPanel1 implements WizardDescriptor.Panel<WizardDescripto
      */
     private SNDefVisualPanel1 component;
 
+    private final Pattern validNames = Pattern.compile("^[a-zA-Z][a-zA-Z\\d]+$");
+
     // Get the visual component for the panel. In this template, the component
     // is kept separate. This can be more efficient: if the wizard is created
     // but never displayed, or not all panels are displayed, it is better to
@@ -65,4 +96,10 @@ public class SNDefWizardPanel1 implements WizardDescriptor.Panel<WizardDescripto
         // use wiz.putProperty to remember current panel state
     }
 
+    @Override
+    public void validate() throws WizardValidationException {
+        if (!validNames.matcher(getComponent().getDefName()).matches()) {
+            throw new WizardValidationException(null, "Invalid ShaderNode Definition Name", null);
+        }
+    }
 }

+ 29 - 3
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/SNDefWizardPanel2.java

@@ -1,7 +1,33 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2022 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.shadernodedefinition.wizard;
 

+ 0 - 5
jme3-materialeditor/src/com/jme3/gde/shadernodedefinition/wizard/sNDef.html

@@ -1,8 +1,3 @@
-<!--
-To change this license header, choose License Headers in Project Properties.
-To change this template file, choose Tools | Templates
-and open the template in the editor.
--->
 <html>
     <head>
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

+ 2 - 0
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties

@@ -71,3 +71,5 @@ SceneComposerTopComponent.jLabel9.text=Camera Position:
 SceneComposerTopComponent.jLabel10.text=Camera Look Direction:
 SceneComposerTopComponent.jLabel11.text=(NaN, NaN, NaN)
 SceneComposerTopComponent.jLabel12.text=(NaN, NaN, NaN)
+SceneComposerTopComponent.cursorPositionLabel.text=(NaN, NaN, NaN)
+SceneComposerTopComponent.cursorPositionHeader.text=Cursor Position:

+ 23 - 3
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.form

@@ -77,17 +77,19 @@
                       <Component id="jSpinner1" min="-2" pref="80" max="-2" attributes="0"/>
                   </Group>
               </Group>
-              <Group type="102" alignment="0" attributes="0">
+              <Group type="102" attributes="0">
                   <Group type="103" groupAlignment="0" attributes="0">
                       <Component id="jLabel9" alignment="0" min="-2" max="-2" attributes="0"/>
                       <Component id="jLabel10" alignment="0" min="-2" max="-2" attributes="0"/>
+                      <Component id="cursorPositionHeader" alignment="0" min="-2" max="-2" attributes="0"/>
                   </Group>
                   <EmptySpace min="-2" pref="34" max="-2" attributes="0"/>
                   <Group type="103" groupAlignment="0" attributes="0">
+                      <Component id="cursorPositionLabel" min="-2" max="-2" attributes="0"/>
                       <Component id="jLabel12" alignment="0" min="-2" max="-2" attributes="0"/>
                       <Component id="jLabel11" alignment="0" min="-2" max="-2" attributes="0"/>
                   </Group>
-                  <EmptySpace max="-2" attributes="0"/>
+                  <EmptySpace max="32767" attributes="0"/>
               </Group>
           </Group>
         </DimensionLayout>
@@ -124,7 +126,11 @@
                       <Component id="jLabel10" alignment="3" min="-2" max="-2" attributes="0"/>
                       <Component id="jLabel12" alignment="3" min="-2" max="-2" attributes="0"/>
                   </Group>
-                  <EmptySpace min="-2" pref="22" max="-2" attributes="0"/>
+                  <EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="3" attributes="0">
+                      <Component id="cursorPositionHeader" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="cursorPositionLabel" alignment="3" min="-2" max="-2" attributes="0"/>
+                  </Group>
               </Group>
           </Group>
         </DimensionLayout>
@@ -226,6 +232,20 @@
             </Property>
           </Properties>
         </Component>
+        <Component class="javax.swing.JLabel" name="cursorPositionHeader">
+          <Properties>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="com/jme3/gde/scenecomposer/Bundle.properties" key="SceneComposerTopComponent.cursorPositionHeader.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JLabel" name="cursorPositionLabel">
+          <Properties>
+            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+              <ResourceString bundle="com/jme3/gde/scenecomposer/Bundle.properties" key="SceneComposerTopComponent.cursorPositionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+            </Property>
+          </Properties>
+        </Component>
       </SubComponents>
     </Container>
     <Container class="javax.swing.JToolBar" name="jToolBar1">

+ 32 - 8
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java

@@ -13,6 +13,7 @@ import com.jme3.gde.core.scene.PreviewRequest;
 import com.jme3.gde.core.scene.SceneApplication;
 import com.jme3.gde.core.scene.SceneListener;
 import com.jme3.gde.core.scene.SceneRequest;
+import com.jme3.gde.core.scene.controller.SceneToolController;
 import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
 import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
@@ -33,6 +34,7 @@ import java.util.Collection;
 import java.util.concurrent.Callable;
 import java.util.logging.Logger;
 import javax.swing.ButtonGroup;
+import javax.swing.SwingUtilities;
 import javax.swing.border.TitledBorder;
 import org.netbeans.api.progress.ProgressHandle;
 import org.netbeans.api.settings.ConvertAsProperties;
@@ -62,7 +64,9 @@ import org.openide.windows.WindowManager;
 @ConvertAsProperties(dtd = "-//com.jme3.gde.scenecomposer//SceneComposer//EN",
         autostore = false)
 @SuppressWarnings("unchecked")
-public final class SceneComposerTopComponent extends TopComponent implements SceneListener, LookupListener {
+public final class SceneComposerTopComponent extends TopComponent implements
+        SceneListener, LookupListener, 
+        SceneToolController.SceneToolControllerListener {
 
     private static SceneComposerTopComponent instance;
     /**
@@ -72,7 +76,7 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce
     private static final String PREFERRED_ID = "SceneComposerTopComponent";
     private final Result<AbstractSceneExplorerNode> result;
     ComposerCameraController camController;
-    SceneComposerToolController toolController;
+    private SceneComposerToolController toolController;
     SceneEditorController editorController;
     CameraPositionTrackerAppState cameraPositionTrackerAppState;
     private SceneRequest sentRequest;
@@ -111,6 +115,8 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce
         jLabel10 = new javax.swing.JLabel();
         jLabel11 = new javax.swing.JLabel();
         jLabel12 = new javax.swing.JLabel();
+        cursorPositionHeader = new javax.swing.JLabel();
+        cursorPositionLabel = new javax.swing.JLabel();
         jToolBar1 = new javax.swing.JToolBar();
         transformationTypeComboBox = new javax.swing.JComboBox<>();
         jSeparator9 = new javax.swing.JToolBar.Separator();
@@ -207,6 +213,10 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce
 
         org.openide.awt.Mnemonics.setLocalizedText(jLabel12, org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.jLabel12.text")); // NOI18N
 
+        org.openide.awt.Mnemonics.setLocalizedText(cursorPositionHeader, org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.cursorPositionHeader.text")); // NOI18N
+
+        org.openide.awt.Mnemonics.setLocalizedText(cursorPositionLabel, org.openide.util.NbBundle.getMessage(SceneComposerTopComponent.class, "SceneComposerTopComponent.cursorPositionLabel.text")); // NOI18N
+
         javax.swing.GroupLayout sceneInfoPanelLayout = new javax.swing.GroupLayout(sceneInfoPanel);
         sceneInfoPanel.setLayout(sceneInfoPanelLayout);
         sceneInfoPanelLayout.setHorizontalGroup(
@@ -228,12 +238,14 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce
             .addGroup(sceneInfoPanelLayout.createSequentialGroup()
                 .addGroup(sceneInfoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                     .addComponent(jLabel9)
-                    .addComponent(jLabel10))
+                    .addComponent(jLabel10)
+                    .addComponent(cursorPositionHeader))
                 .addGap(34, 34, 34)
                 .addGroup(sceneInfoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(cursorPositionLabel)
                     .addComponent(jLabel12)
                     .addComponent(jLabel11))
-                .addContainerGap())
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
         );
         sceneInfoPanelLayout.setVerticalGroup(
             sceneInfoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -262,7 +274,10 @@ public final class SceneComposerTopComponent extends TopComponent implements Sce
                 .addGroup(sceneInfoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                     .addComponent(jLabel10)
                     .addComponent(jLabel12))
-                .addGap(22, 22, 22))
+                .addGap(8, 8, 8)
+                .addGroup(sceneInfoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(cursorPositionHeader)
+                    .addComponent(cursorPositionLabel)))
         );
 
         jToolBar1.setFloatable(false);
@@ -819,6 +834,8 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JButton camToCursorSelectionButton;
     private javax.swing.JButton createPhysicsMeshButton;
+    private javax.swing.JLabel cursorPositionHeader;
+    private javax.swing.JLabel cursorPositionLabel;
     private javax.swing.JButton cursorToSelectionButton;
     private javax.swing.JButton emitButton;
     private javax.swing.JCheckBox fixedCheckBox;
@@ -1202,7 +1219,7 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
                 toolController.cleanup();
             }
             toolController = new SceneComposerToolController(request.getToolNode(), request.getManager(), request.getJmeNode());
-
+            toolController.setToolListener(this);
             camController = new ComposerCameraController(SceneApplication.getApplication().getCamera(), request.getJmeNode());
             toolController.setEditorController(editorController);
             camController.setToolController(toolController);
@@ -1284,10 +1301,10 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
     public void sceneClosed(SceneRequest request) {
         if (request.equals(currentRequest)) {
             setActivatedNodes(new org.openide.nodes.Node[]{});
-            if (listener != null && request.getManager() != null) {
-//                request.getManager().removeClassPathEventListener(listener);
+            if(request.getManager() != null && listener != null) {
                 listener = null;
             }
+
             SceneApplication.getApplication().removeSceneListener(this);
             SceneApplication.getApplication().getStateManager().detach(cameraPositionTrackerAppState);
             currentRequest = null;
@@ -1308,4 +1325,11 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
     public SceneComposerToolController getToolController() {
         return toolController;
     }
+
+    @Override
+    public void onSetCursorLocation(final Vector3f location) {
+        SwingUtilities.invokeLater(() -> {
+            cursorPositionLabel.setText(location.toString());
+        });
+    }
 }

BIN
jme3-templates/src/com/jme3/gde/templates/GradleDesktopGameProject.zip


BIN
nbi/antlib/nbi-ant-tasks.jar


BIN
nbi/antlib/nbi-engine.jar


BIN
nbi/antlib/nbi-registries-management.jar


+ 1 - 1
nbi/antlib/version

@@ -1 +1 @@
-NB-11.2.0-custom-patch-MeFisto94-bypass-NETBEANS-3342
+NB-14

+ 0 - 3
nbproject/platform.properties

@@ -39,19 +39,16 @@ disabled.modules=\
     org.netbeans.modules.bugtracking.commons,\
     org.netbeans.modules.bugzilla,\
     org.netbeans.modules.bugzilla.exceptionreporter,\
-    org.netbeans.modules.db,\
     org.netbeans.modules.db.core,\
     org.netbeans.modules.db.dataview,\
     org.netbeans.modules.db.drivers,\
     org.netbeans.modules.db.kit,\
-    org.netbeans.modules.db.metadata.model,\
     org.netbeans.modules.db.mysql,\
     org.netbeans.modules.db.sql.editor,\
     org.netbeans.modules.db.sql.visualeditor,\
     org.netbeans.modules.dbapi,\
     org.netbeans.modules.dbschema,\
     org.netbeans.modules.deadlock.detector,\
-    org.netbeans.modules.debugger.jpda.truffle,\
     org.netbeans.modules.debugger.jpda.trufflenode,\
     org.netbeans.modules.derby,\
     org.netbeans.modules.form,\

+ 2 - 2
nbproject/project.properties

@@ -7,9 +7,9 @@ app.description=A complete 3D game development suite written purely in Java.
 app.categories=Development,Graphics,IDE,3DGraphics,Java
 app.icon.icns=jmonkeyplatform.icns
 #version name used for application and settings folder, no spaces!
-app.version=3.3.0-SNAPSHOT
+app.version=3.4.1-SNAPSHOT
 #version number used for plugins, only 3 numbers (e.g. 3.1.3)
-plugins.version=3.3.0
+plugins.version=3.4.1
 nbm.revision=2026
 #command line args
 run.args.extra=-J-Dsun.java2d.dpiaware\=true -J-Dapple.laf.useScreenMenuBar\=true -J-Dawt.useSystemAAFontSettings\=lcd -J-Dswing.aatext\=true -J-Xmx512m -J-XX\:MaxDirectMemorySize\=2048m -J-Dsun.zip.disableMemoryMapping\=true -J-Dapple.awt.graphics.UseQuartz\=true -J-Dsun.java2d.noddraw\=true

BIN
resources/splashscreen/splash.gif


BIN
resources/splashscreen/splash.psd


BIN
resources/splashscreen/splash2.psd


BIN
resources/splashscreen/splash2_jme32.psd


BIN
resources/splashscreen/splash_jme32.psd


BIN
resources/splashscreen/updatersplash.gif


+ 2 - 2
version.gradle

@@ -137,8 +137,8 @@ task configureVersionInfo {
         jmeShortGitHash = head.abbreviatedId
         jmeBranchName = grgit.branch.current.name
         
-        if (System.env.TRAVIS_TAG != null) {
-            jmeGitTag = System.env.TRAVIS_TAG
+        if (project.hasProperty("tag_name")) {
+            jmeGitTag = project.getProperty("tag_name")
         } else {
             jmeGitTag = grgit.tag.list().find { it.commit == head }
         }