From 7f2debb25f2f247e2754f5d3a49d187283f87205 Mon Sep 17 00:00:00 2001 From: kgv Date: Fri, 7 Nov 2014 12:20:13 +0400 Subject: [PATCH] 0025195: Samples - add Java sample for Android 4.x --- samples/java/jniviewer/.classpath | 9 + .../.externalToolBuilders/C++ Builder.launch | 12 + samples/java/jniviewer/.gitignore | 4 + samples/java/jniviewer/.project | 43 + .../.settings/org.eclipse.jdt.core.prefs | 4 + samples/java/jniviewer/AndroidManifest.xml | 36 + samples/java/jniviewer/ReadMe.md | 58 ++ samples/java/jniviewer/jni/Android.mk | 215 +++++ samples/java/jniviewer/jni/Application.mk | 8 + .../java/jniviewer/jni/OcctJni_MsgPrinter.cxx | 102 +++ .../java/jniviewer/jni/OcctJni_MsgPrinter.hxx | 62 ++ samples/java/jniviewer/jni/OcctJni_Viewer.cxx | 810 ++++++++++++++++++ samples/java/jniviewer/jni/OcctJni_Viewer.hxx | 88 ++ samples/java/jniviewer/jni/OcctJni_Window.cxx | 17 + samples/java/jniviewer/jni/OcctJni_Window.hxx | 106 +++ samples/java/jniviewer/project.properties | 14 + .../jniviewer/res/drawable-hdpi/close_l.png | Bin 0 -> 1410 bytes .../jniviewer/res/drawable-hdpi/close_p.png | Bin 0 -> 1325 bytes .../java/jniviewer/res/drawable-hdpi/fit.png | Bin 0 -> 1002 bytes .../res/drawable-hdpi/ic_launcher.png | Bin 0 -> 2591 bytes .../java/jniviewer/res/drawable-hdpi/info.png | Bin 0 -> 1660 bytes .../res/drawable-hdpi/info_image.png | Bin 0 -> 24204 bytes .../jniviewer/res/drawable-hdpi/message.png | Bin 0 -> 443 bytes .../java/jniviewer/res/drawable-hdpi/open.png | Bin 0 -> 917 bytes .../jniviewer/res/drawable-hdpi/open_l.png | Bin 0 -> 1415 bytes .../jniviewer/res/drawable-hdpi/open_p.png | Bin 0 -> 1322 bytes .../jniviewer/res/drawable-hdpi/proj_back.png | Bin 0 -> 1716 bytes .../res/drawable-hdpi/proj_bottom.png | Bin 0 -> 1867 bytes .../res/drawable-hdpi/proj_front.png | Bin 0 -> 1604 bytes .../jniviewer/res/drawable-hdpi/proj_left.png | Bin 0 -> 1771 bytes .../res/drawable-hdpi/proj_right.png | Bin 0 -> 1644 bytes .../jniviewer/res/drawable-hdpi/proj_top.png | Bin 0 -> 1862 bytes .../java/jniviewer/res/drawable-hdpi/view.png | Bin 0 -> 839 bytes .../res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1609 bytes .../res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 3669 bytes .../res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 5560 bytes .../jniviewer/res/layout/activity_main.xml | 166 ++++ samples/java/jniviewer/res/values/id.xml | 17 + samples/java/jniviewer/res/values/strings.xml | 35 + .../jnisample/OcctJniActivity.java | 778 +++++++++++++++++ .../jnisample/OcctJniFileDialog.java | 376 ++++++++ .../opencascade/jnisample/OcctJniLogger.java | 71 ++ .../jnisample/OcctJniRenderer.java | 218 +++++ .../opencascade/jnisample/OcctJniView.java | 332 +++++++ 44 files changed, 3581 insertions(+) create mode 100644 samples/java/jniviewer/.classpath create mode 100644 samples/java/jniviewer/.externalToolBuilders/C++ Builder.launch create mode 100644 samples/java/jniviewer/.gitignore create mode 100644 samples/java/jniviewer/.project create mode 100644 samples/java/jniviewer/.settings/org.eclipse.jdt.core.prefs create mode 100644 samples/java/jniviewer/AndroidManifest.xml create mode 100644 samples/java/jniviewer/ReadMe.md create mode 100644 samples/java/jniviewer/jni/Android.mk create mode 100644 samples/java/jniviewer/jni/Application.mk create mode 100644 samples/java/jniviewer/jni/OcctJni_MsgPrinter.cxx create mode 100644 samples/java/jniviewer/jni/OcctJni_MsgPrinter.hxx create mode 100644 samples/java/jniviewer/jni/OcctJni_Viewer.cxx create mode 100644 samples/java/jniviewer/jni/OcctJni_Viewer.hxx create mode 100644 samples/java/jniviewer/jni/OcctJni_Window.cxx create mode 100644 samples/java/jniviewer/jni/OcctJni_Window.hxx create mode 100644 samples/java/jniviewer/project.properties create mode 100644 samples/java/jniviewer/res/drawable-hdpi/close_l.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/close_p.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/fit.png create mode 100755 samples/java/jniviewer/res/drawable-hdpi/ic_launcher.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/info.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/info_image.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/message.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/open.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/open_l.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/open_p.png create mode 100755 samples/java/jniviewer/res/drawable-hdpi/proj_back.png create mode 100755 samples/java/jniviewer/res/drawable-hdpi/proj_bottom.png create mode 100755 samples/java/jniviewer/res/drawable-hdpi/proj_front.png create mode 100755 samples/java/jniviewer/res/drawable-hdpi/proj_left.png create mode 100755 samples/java/jniviewer/res/drawable-hdpi/proj_right.png create mode 100755 samples/java/jniviewer/res/drawable-hdpi/proj_top.png create mode 100644 samples/java/jniviewer/res/drawable-hdpi/view.png create mode 100755 samples/java/jniviewer/res/drawable-mdpi/ic_launcher.png create mode 100755 samples/java/jniviewer/res/drawable-xhdpi/ic_launcher.png create mode 100755 samples/java/jniviewer/res/drawable-xxhdpi/ic_launcher.png create mode 100644 samples/java/jniviewer/res/layout/activity_main.xml create mode 100644 samples/java/jniviewer/res/values/id.xml create mode 100644 samples/java/jniviewer/res/values/strings.xml create mode 100644 samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniActivity.java create mode 100644 samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniFileDialog.java create mode 100644 samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniLogger.java create mode 100644 samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniRenderer.java create mode 100644 samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniView.java diff --git a/samples/java/jniviewer/.classpath b/samples/java/jniviewer/.classpath new file mode 100644 index 0000000000..51769745b2 --- /dev/null +++ b/samples/java/jniviewer/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/java/jniviewer/.externalToolBuilders/C++ Builder.launch b/samples/java/jniviewer/.externalToolBuilders/C++ Builder.launch new file mode 100644 index 0000000000..cbe7a127ce --- /dev/null +++ b/samples/java/jniviewer/.externalToolBuilders/C++ Builder.launch @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/samples/java/jniviewer/.gitignore b/samples/java/jniviewer/.gitignore new file mode 100644 index 0000000000..890072f0ac --- /dev/null +++ b/samples/java/jniviewer/.gitignore @@ -0,0 +1,4 @@ +/assets +/bin +/gen +/libs diff --git a/samples/java/jniviewer/.project b/samples/java/jniviewer/.project new file mode 100644 index 0000000000..c258add62f --- /dev/null +++ b/samples/java/jniviewer/.project @@ -0,0 +1,43 @@ + + + occtJniActivity + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.ui.externaltools.ExternalToolBuilder + auto,full,incremental, + + + LaunchConfigHandle + <project>/.externalToolBuilders/C++ Builder.launch + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/samples/java/jniviewer/.settings/org.eclipse.jdt.core.prefs b/samples/java/jniviewer/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..b080d2ddc8 --- /dev/null +++ b/samples/java/jniviewer/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/samples/java/jniviewer/AndroidManifest.xml b/samples/java/jniviewer/AndroidManifest.xml new file mode 100644 index 0000000000..c0cbbbbfe8 --- /dev/null +++ b/samples/java/jniviewer/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/java/jniviewer/ReadMe.md b/samples/java/jniviewer/ReadMe.md new file mode 100644 index 0000000000..c1dd21d7f9 --- /dev/null +++ b/samples/java/jniviewer/ReadMe.md @@ -0,0 +1,58 @@ +OCCT JniViewer sample for Android {#samples_java_android_occt} +================== + +This sample demonstrates simple way of using OCCT libraries in Android application written using Java. + +The connection between Java and OCCT (C++) level is provided by proxy library, libTKJniSample.so, written in C++ with exported JNI methods of Java class OcctJniRenderer. +The proxy library contains single C++ class OcctJni_Viewer encapsulating OCCT viewer and providing functionality to manipulate this viewer +and to import OCCT shapes from several supported formats of CAD files (IGES, STEP, BREP). + +This sample demonstrates indirect method of wrapping C++ to Java using manually created proxy library. +Alternative method is available, wrapping individual OCCT classes to Java equivalents so that their full API is available to Java user +and the code can be programmed on Java level similarly to C++ one. +See description of OCCT Java Wrapper in Advanced Samples and Tools on OCCT web site at +http://www.opencascade.org/support/products/advsamples + +Run Eclipse from ADT (Android Developer Tools) for building the sample. To import sample project perform +~~~~ + File -> Import... -> Android -> Existing Android code into Workspace +~~~~ +and specify this directory. The project re-build will be started immediately right after importation if "Build automatically" option is turned on (default in Eclipse). +Proxy library compilation and packaging is performed by NDK build script, called by "C++ Builder" configured within Eclipse project. +The path to "ndk-build" tool from Android NDK (Native Development Kit) should be specified in Eclipse project properties: +~~~~ + Project -> Properties -> Builders -> C++ Builder -> Edit -> Location +~~~~ + +Now paths to OCCT C++ libraries and additional components should be specified in "jni/Android.mk" file: +~~~~ +OCCT_ROOT := $(LOCAL_PATH)/../../../.. + +FREETYPE_INC := $(OCCT_ROOT)/../freetype/include/freetype2 +FREETYPE_LIBS := $(OCCT_ROOT)/../freetype/libs + +FREEIMAGE_INC := $(OCCT_ROOT)/../FreeImage/include +FREEIMAGE_LIBS := $(OCCT_ROOT)/../FreeImage/libs + +OCCT_INC := $(OCCT_ROOT)/inc +OCCT_LIBS := $(OCCT_ROOT)/and/libs +~~~~ +The list of extra components (Freetype, FreeImage) depends on OCCT configuration. +Variable $(TARGET_ARCH_ABI) is used within this script to refer to active architecture. +E.g. for 32-bit ARM build (see variable *APP_ABI* in "jni/Application.mk") +the folder *OCCT_LIBS* should contain sub-folder "armeabi-v7a" with OCCT libraries. + +FreeImage is optional and does not required for this sample, however you should include all extra libraries used for OCCT building +and load the explicitly from Java code within OcctJniActivity::loadNatives() method, including toolkits from OCCT itself in proper order: +~~~~ + if (!loadLibVerbose ("TKernel", aLoaded, aFailed) + || !loadLibVerbose ("TKMath", aLoaded, aFailed) + || !loadLibVerbose ("TKG2d", aLoaded, aFailed) +~~~~ +Note that C++ STL library is not part of Android system. +Thus application must package this library as well as extra component. +"gnustl_shared" STL implementation is expected within this sample. + +After successful build, the application can be packaged to Android: +- Deploy and run application on connected device or emulator directly from Eclipse using adb interface by menu items "Run" and "Debug". This would sign package with debug certificate. +- Prepare signed end-user package using wizard File -> Export -> Android -> Export Android Application. diff --git a/samples/java/jniviewer/jni/Android.mk b/samples/java/jniviewer/jni/Android.mk new file mode 100644 index 0000000000..912d5e3be3 --- /dev/null +++ b/samples/java/jniviewer/jni/Android.mk @@ -0,0 +1,215 @@ +LOCAL_PATH:= $(call my-dir) + +STL_INC := $(NDK_ROOT)/sources/cxx-stl/gnu-libstdc++/$(NDK_TOOLCHAIN_VERSION)/include $(NDK_ROOT)/sources/cxx-stl/gnu-libstdc++/$(NDK_TOOLCHAIN_VERSION)/libs/$(TARGET_ARCH_ABI)/include +#STL_LIB := $(NDK_ROOT)/sources/cxx-stl/gnu-libstdc++/$(NDK_TOOLCHAIN_VERSION)/libs/$(TARGET_ARCH_ABI)/libgnustl_static.a +STL_LIB := $(NDK_ROOT)/sources/cxx-stl/gnu-libstdc++/$(NDK_TOOLCHAIN_VERSION)/libs/$(TARGET_ARCH_ABI)/libgnustl_shared.so + +OCCT_ROOT := $(LOCAL_PATH)/../../../.. + +FREETYPE_INC := $(OCCT_ROOT)/../freetype/include/freetype2 +FREETYPE_LIBS := $(OCCT_ROOT)/../freetype/libs + +FREEIMAGE_INC := $(OCCT_ROOT)/../FreeImage/include +FREEIMAGE_LIBS := $(OCCT_ROOT)/../FreeImage/libs + +OCCT_INC := $(OCCT_ROOT)/inc +OCCT_LIBS := $(OCCT_ROOT)/and/libs + +ASSETDIR := $(LOCAL_PATH)/../assets + +$(ASSETDIR)/Shaders: $(ASSETDIR) + -mkdir -p $(ASSETDIR) + -mkdir -p $(ASSETDIR)/Shaders + cp -f -r $(OCCT_ROOT)/src/Shaders/*.* $(ASSETDIR)/Shaders + +$(ASSETDIR)/SHMessage: $(ASSETDIR) + -mkdir -p $(ASSETDIR) + -mkdir -p $(ASSETDIR)/SHMessage + cp -f -r $(OCCT_ROOT)/src/SHMessage/*.* $(ASSETDIR)/SHMessage + +$(ASSETDIR)/XSMessage: $(ASSETDIR) + -mkdir -p $(ASSETDIR) + -mkdir -p $(ASSETDIR)/XSMessage + cp -f -r $(OCCT_ROOT)/src/XSMessage/*.* $(ASSETDIR)/XSMessage + +$(ASSETDIR)/TObj: $(ASSETDIR) + -mkdir -p $(ASSETDIR) + -mkdir -p $(ASSETDIR)/TObj + cp -f -r $(OCCT_ROOT)/src/TObj/*.msg $(ASSETDIR)/TObj + +$(ASSETDIR)/UnitsAPI: $(ASSETDIR) + -mkdir -p $(ASSETDIR) + -mkdir -p $(ASSETDIR)/UnitsAPI + cp -f -r $(OCCT_ROOT)/src/UnitsAPI/*.dat $(ASSETDIR)/UnitsAPI + +pre_all: $(ASSETDIR)/Shaders $(ASSETDIR)/SHMessage $(ASSETDIR)/XSMessage $(ASSETDIR)/TObj $(ASSETDIR)/UnitsAPI + +jniall: pre_all all + +# STL libs +include $(CLEAR_VARS) +LOCAL_MODULE := SharedStl +LOCAL_EXPORT_C_INCLUDES := $(STL_INC) +LOCAL_SRC_FILES := $(STL_LIB) +include $(PREBUILT_SHARED_LIBRARY) + +# 3rd-parties used in OCCT +include $(CLEAR_VARS) +LOCAL_MODULE := FreeType +LOCAL_EXPORT_C_INCLUDES := $(FREETYPE_INC) +LOCAL_SRC_FILES := $(FREETYPE_LIBS)/$(TARGET_ARCH_ABI)/libfreetype.so +include $(PREBUILT_SHARED_LIBRARY) + +#include $(CLEAR_VARS) +#LOCAL_MODULE := FreeImage +#LOCAL_EXPORT_C_INCLUDES := $(FREEIMAGE_INC) +#LOCAL_SRC_FILES := $(FREEIMAGE_LIBS)/$(TARGET_ARCH_ABI)/libfreeimage.so +#include $(PREBUILT_SHARED_LIBRARY) + +# OCCT core +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKernel +LOCAL_EXPORT_C_INCLUDES := $(OCCT_INC) +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKernel.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKMath +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKMath.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKG2d +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKG2d.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKG3d +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKG3d.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKGeomBase +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKGeomBase.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKBRep +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKBRep.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKGeomAlgo +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKGeomAlgo.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKTopAlgo +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKTopAlgo.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKShHealing +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKShHealing.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKMesh +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKMesh.so +include $(PREBUILT_SHARED_LIBRARY) + +# OCCT Exchange +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKPrim +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKPrim.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKBO +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKBO.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKBool +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKBool.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKFillet +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKFillet.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKOffset +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKOffset.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKXSBase +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKXSBase.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKIGES +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKIGES.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKSTEPBase +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKSTEPBase.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKSTEPAttr +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKSTEPAttr.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKSTEP209 +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKSTEP209.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKSTEP +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKSTEP.so +include $(PREBUILT_SHARED_LIBRARY) + +# OCCT visualization +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKService +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKService.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKHLR +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKHLR.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKV3d +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKV3d.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OcctTKOpenGl +LOCAL_SRC_FILES := $(OCCT_LIBS)/$(TARGET_ARCH_ABI)/libTKOpenGl.so +include $(PREBUILT_SHARED_LIBRARY) + +# our sample +include $(CLEAR_VARS) +LOCAL_MODULE := libTKJniSample +LOCAL_C_INCLUDES := $(STL_INC) +#LOCAL_STATIC_LIBRARIES := $(STL_LIB) does not work +LOCAL_CFLAGS := -Wall +LOCAL_CPP_EXTENSION := .cxx .cpp +LOCAL_CPP_FEATURES := rtti exceptions +LOCAL_SRC_FILES := OcctJni_Viewer.cxx OcctJni_Window.cxx OcctJni_MsgPrinter.cxx +LOCAL_SHARED_LIBRARIES := OcctTKernel OcctTKMath OcctTKG2d OcctTKG3d OcctTKGeomBase OcctTKBRep OcctTKGeomAlgo OcctTKTopAlgo OcctTKShHealing OcctTKMesh OcctTKPrim +LOCAL_SHARED_LIBRARIES += OcctTKIGES OcctTKSTEP OcctTKXSBase +LOCAL_SHARED_LIBRARIES += OcctTKService OcctTKHLR OcctTKV3d OcctTKOpenGl +LOCAL_SHARED_LIBRARIES += SharedStl +LOCAL_LDLIBS := -llog -lGLESv2 -lEGL + +#LOCAL_LDLIBS += $(STL_LIB) + +include $(BUILD_SHARED_LIBRARY) diff --git a/samples/java/jniviewer/jni/Application.mk b/samples/java/jniviewer/jni/Application.mk new file mode 100644 index 0000000000..1d6b63382a --- /dev/null +++ b/samples/java/jniviewer/jni/Application.mk @@ -0,0 +1,8 @@ +NDK_TOOLCHAIN_VERSION := 4.8 +APP_PLATFORM := android-15 + +APP_ABI := armeabi-v7a +#APP_ABI := all + +#APP_STL := gnustl_static +#APP_STL := stlport_static diff --git a/samples/java/jniviewer/jni/OcctJni_MsgPrinter.cxx b/samples/java/jniviewer/jni/OcctJni_MsgPrinter.cxx new file mode 100644 index 0000000000..8eef25f58c --- /dev/null +++ b/samples/java/jniviewer/jni/OcctJni_MsgPrinter.cxx @@ -0,0 +1,102 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include + +#include + +IMPLEMENT_STANDARD_HANDLE (OcctJni_MsgPrinter, Message_Printer) +IMPLEMENT_STANDARD_RTTIEXT(OcctJni_MsgPrinter, Message_Printer) + +// ======================================================================= +// function : OcctJni_MsgPrinter +// purpose : +// ======================================================================= +OcctJni_MsgPrinter::OcctJni_MsgPrinter (JNIEnv* theJEnv, + jobject theJObj) +: myJEnv (theJEnv), + myJObj (theJEnv->NewGlobalRef (theJObj)), + myJMet (NULL) +{ + jclass aJClass = theJEnv->GetObjectClass (theJObj); + myJMet = theJEnv->GetMethodID (aJClass, "postMessage", "(Ljava/lang/String;)V"); + if (myJMet == NULL) + { + __android_log_write (ANDROID_LOG_FATAL, "jniSample", "Broken initialization of OcctJni_MsgPrinter!"); + } +} + +// ======================================================================= +// function : ~OcctJni_MsgPrinter +// purpose : +// ======================================================================= +OcctJni_MsgPrinter::~OcctJni_MsgPrinter() +{ + //myJEnv->DeleteGlobalRef (myJObj); +} + +// ======================================================================= +// function : Send +// purpose : +// ======================================================================= +void OcctJni_MsgPrinter::Send (const TCollection_ExtendedString& theString, + const Message_Gravity theGravity, + const Standard_Boolean theToPutEndl) const +{ + if (theGravity >= myTraceLevel) + { + const TCollection_AsciiString aStr (theString); + OcctJni_MsgPrinter::Send (aStr, theGravity, theToPutEndl); + } +} + +// ======================================================================= +// function : Send +// purpose : +// ======================================================================= +void OcctJni_MsgPrinter::Send (const TCollection_AsciiString& theString, + const Message_Gravity theGravity, + const Standard_Boolean theToPutEndl) const +{ + if (theGravity < myTraceLevel) + { + return; + } + + ///__android_log_write (ANDROID_LOG_DEBUG, "OcctJni_MsgPrinter", (TCollection_AsciiString(" @@ ") + theString).ToCString()); + if (myJMet == NULL) + { + return; + } + + jstring aJStr = myJEnv->NewStringUTF ((theString + "\n").ToCString()); + myJEnv->CallObjectMethod (myJObj, myJMet, aJStr); + myJEnv->DeleteLocalRef (aJStr); +} + +// ======================================================================= +// function : Send +// purpose : +// ======================================================================= +void OcctJni_MsgPrinter::Send (const Standard_CString& theString, + const Message_Gravity theGravity, + const Standard_Boolean theToPutEndl) const +{ + if (theGravity >= myTraceLevel) + { + OcctJni_MsgPrinter::Send (TCollection_AsciiString (theString), theGravity, theToPutEndl); + } +} diff --git a/samples/java/jniviewer/jni/OcctJni_MsgPrinter.hxx b/samples/java/jniviewer/jni/OcctJni_MsgPrinter.hxx new file mode 100644 index 0000000000..0735033b4d --- /dev/null +++ b/samples/java/jniviewer/jni/OcctJni_MsgPrinter.hxx @@ -0,0 +1,62 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef OcctJni_MsgPrinter_H +#define OcctJni_MsgPrinter_H + +#include + +#include + +// Class providing connection between messenger interfaces in C++ and Java layers. +class OcctJni_MsgPrinter : public Message_Printer +{ +public: + + //! Default constructor + OcctJni_MsgPrinter (JNIEnv* theJEnv, + jobject theJObj); + + //! Destructor. + ~OcctJni_MsgPrinter(); + + //! Redirection to TCollection_AsciiString method + virtual void Send (const TCollection_ExtendedString& theString, + const Message_Gravity theGravity, + const Standard_Boolean theToPutEndl) const; + + //! Redirection to TCollection_AsciiString method + virtual void Send (const Standard_CString& theString, + const Message_Gravity theGravity, + const Standard_Boolean theToPutEndl) const; + + //! Main printing method + virtual void Send (const TCollection_AsciiString& theString, + const Message_Gravity theGravity, + const Standard_Boolean theToPutEndl) const; + +private: + + JNIEnv* myJEnv; + jobject myJObj; + jmethodID myJMet; + +public: + + DEFINE_STANDARD_RTTI(OcctJni_MsgPrinter) + +}; + +DEFINE_STANDARD_HANDLE(OcctJni_MsgPrinter, Message_Printer) + +#endif // OcctJni_MsgPrinter_H diff --git a/samples/java/jniviewer/jni/OcctJni_Viewer.cxx b/samples/java/jniviewer/jni/OcctJni_Viewer.cxx new file mode 100644 index 0000000000..750f030e91 --- /dev/null +++ b/samples/java/jniviewer/jni/OcctJni_Viewer.cxx @@ -0,0 +1,810 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +//! @return true if file exists +static bool isFileExist (const TCollection_AsciiString& thePath) +{ + struct stat64 aStatBuffer; + return stat64 (thePath.ToCString(), &aStatBuffer) == 0; +} + +//! Cut-off the last split character from the path and everything after it. +static TCollection_AsciiString getParentDir (const TCollection_AsciiString& thePath) +{ + TCollection_AsciiString aPath = thePath; + char* aSplitter = (char* )aPath.ToCString(); + for (char* anIter = aSplitter; *anIter != '\0'; ++anIter) + { + if (*anIter == '\\' + || *anIter == '/') + { + aSplitter = anIter; + } + } + *aSplitter = '\0'; // cut off file name or trailing folder + return TCollection_AsciiString (aPath.ToCString()); +} + +//! Set environment variable theVarName indicating location of resource +//! file theFile so as to correspond to actual location of this file. +//! +//! The resource file is searched in directory where Test.Draw.dll is located, +//! and if not found - also in subdirectory ../res from there. +//! If file is found, environment variable is set for C subsystem. +//! Otherwise, environment is not changed. +//! +//! If theToAddFileName is true, complete file name is set as value of the variable, +//! if theToAddFileName is false, only path is set. +Standard_Boolean setResourceEnv (const TCollection_AsciiString& theVarName, + const TCollection_AsciiString& theRoot, + const TCollection_AsciiString& theFile, + const Standard_Boolean theToAddFileName) +{ + // use location of current assembly to figure out possible location of resource + TCollection_AsciiString aBaseDir = theRoot; + + // check the same directory where binary is located + if (!isFileExist (aBaseDir + "/" + theFile)) + { + // check subdirectory ../res + aBaseDir = getParentDir (aBaseDir) + "/res"; + if (!isFileExist (aBaseDir + "/" + theFile)) + { + return Standard_False; + } + } + + // set C library environment + if (theToAddFileName) + { + aBaseDir = aBaseDir + "/" + theFile; + } + + OSD_Environment anEnv (theVarName, aBaseDir); + anEnv.Build(); + return Standard_True; +} + +// ======================================================================= +// function : OcctJni_Viewer +// purpose : +// ======================================================================= +OcctJni_Viewer::OcctJni_Viewer() +{ + // prepare necessary environment + TCollection_AsciiString aResRoot = "/data/data/com.opencascade.jnisample/files"; + + setResourceEnv ("CSF_TObjMessage", aResRoot + "/TObj", "TObj.msg", Standard_False); + setResourceEnv ("CSF_UnitsLexicon", aResRoot + "/UnitsAPI", "Lexi_Expr.dat", Standard_True); + setResourceEnv ("CSF_UnitsDefinition", aResRoot + "/UnitsAPI", "Units.dat", Standard_True); + setResourceEnv ("CSF_ShadersDirectory", aResRoot + "/Shaders", "Declarations.glsl", Standard_False); + setResourceEnv ("CSF_XSMessage", aResRoot + "/XSMessage", "XSTEP.us", Standard_False); + setResourceEnv ("CSF_SHMessage", aResRoot + "/XSMessage", "SHAPE.us", Standard_False); + //setResourceEnv ("CSF_PluginDefaults", "Plugin", Standard_False); + + // make sure OCCT loads the dictionary + //UnitsAPI::SetLocalSystem (UnitsAPI_SI); + + // load messages for TObj + Message_MsgFile::LoadFromEnv ("CSF_TObjMessage", "TObj", "msg"); +} + +// ======================================================================= +// function : init +// purpose : +// ======================================================================= +bool OcctJni_Viewer::init() +{ + EGLint aCfgId = 0; + int aWidth = 0, aHeight = 0; + EGLDisplay anEglDisplay = eglGetCurrentDisplay(); + EGLContext anEglContext = eglGetCurrentContext(); + EGLSurface anEglSurf = eglGetCurrentSurface (EGL_DRAW); + if (anEglDisplay == EGL_NO_DISPLAY + || anEglContext == EGL_NO_CONTEXT + || anEglSurf == EGL_NO_SURFACE) + { + Message::DefaultMessenger()->Send ("Error: No active EGL context!", Message_Fail); + release(); + return false; + } + + eglQuerySurface (anEglDisplay, anEglSurf, EGL_WIDTH, &aWidth); + eglQuerySurface (anEglDisplay, anEglSurf, EGL_HEIGHT, &aHeight); + eglQuerySurface (anEglDisplay, anEglSurf, EGL_CONFIG_ID, &aCfgId); + const EGLint aConfigAttribs[] = { EGL_CONFIG_ID, aCfgId, EGL_NONE }; + EGLint aNbConfigs = 0; + void* anEglConfig = NULL; + if (eglChooseConfig (anEglDisplay, aConfigAttribs, &anEglConfig, 1, &aNbConfigs) != EGL_TRUE) + { + Message::DefaultMessenger()->Send ("Error: EGL does not provide compatible configurations!", Message_Fail); + release(); + return false; + } + + TCollection_AsciiString anEglInfo = TCollection_AsciiString() + + "\n EGLVersion: " + eglQueryString (anEglDisplay, EGL_VERSION) + + "\n EGLVendor: " + eglQueryString (anEglDisplay, EGL_VENDOR) + + "\n EGLClient APIs: " + eglQueryString (anEglDisplay, EGL_CLIENT_APIS) + + "\n GLvendor: " + (const char* )glGetString (GL_VENDOR) + + "\n GLdevice: " + (const char* )glGetString (GL_RENDERER) + + "\n GLversion: " + (const char* )glGetString (GL_VERSION) + " [GLSL: " + (const char* )glGetString (GL_SHADING_LANGUAGE_VERSION) + "]"; + ::Message::DefaultMessenger()->Send (anEglInfo, Message_Info); + + if (!myViewer.IsNull()) + { + Handle(OpenGl_GraphicDriver) aDriver = Handle(OpenGl_GraphicDriver)::DownCast (myViewer->Driver()); + Handle(OcctJni_Window) aWindow = Handle(OcctJni_Window)::DownCast (myView->Window()); + if (!aDriver->InitEglContext (anEglDisplay, anEglContext, anEglConfig)) + { + Message::DefaultMessenger()->Send ("Error: OpenGl_GraphicDriver can not be initialized!", Message_Fail); + release(); + return false; + } + + aWindow->SetSize (aWidth, aHeight); + myView->SetWindow (aWindow, (Aspect_RenderingContext )anEglContext, NULL, NULL); + return true; + } + + Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (NULL, Standard_False); + aDriver->ChangeOptions().buffersNoSwap = Standard_True; +//aDriver->ChangeOptions().glslWarnings = Standard_True; /// for debug only! + if (!aDriver->InitEglContext (anEglDisplay, anEglContext, anEglConfig)) + { + Message::DefaultMessenger()->Send ("Error: OpenGl_GraphicDriver can not be initialized!", Message_Fail); + release(); + return false; + } + + // create viewer + myViewer = new V3d_Viewer (aDriver, TCollection_ExtendedString("Viewer").ToExtString(), "", 1000.0, + V3d_XposYnegZpos, Quantity_NOC_BLACK, V3d_ZBUFFER, V3d_GOURAUD, V3d_WAIT, + Standard_True, Standard_False); + myViewer->SetDefaultLights(); + myViewer->SetLightOn(); + + // create AIS context + myContext = new AIS_InteractiveContext (myViewer); + //myContext->SetDisplayMode (AIS_WireFrame); + myContext->SetDisplayMode (AIS_Shaded); + + Handle(OcctJni_Window) aWindow = new OcctJni_Window (aWidth, aHeight); + myView = myViewer->CreateView(); + + myView->SetWindow (aWindow, (Aspect_RenderingContext )anEglContext, NULL, NULL); + myView->TriedronDisplay (Aspect_TOTP_RIGHT_LOWER, Quantity_NOC_WHITE, 0.08, V3d_ZBUFFER); + + initContent(); + return true; +} + +// ======================================================================= +// function : release +// purpose : +// ======================================================================= +void OcctJni_Viewer::release() +{ + myContext.Nullify(); + myView.Nullify(); + myViewer.Nullify(); +} + +// ======================================================================= +// function : resize +// purpose : +// ======================================================================= +void OcctJni_Viewer::resize (int theWidth, + int theHeight) +{ + if (myContext.IsNull()) + { + Message::DefaultMessenger()->Send ("Resize failed - view is unavailable", Message_Fail); + return; + } + + Handle(OpenGl_GraphicDriver) aDriver = Handle(OpenGl_GraphicDriver)::DownCast (myViewer->Driver()); + Handle(OcctJni_Window) aWindow = Handle(OcctJni_Window)::DownCast (myView->Window()); + aWindow->SetSize (theWidth, theHeight); + //myView->MustBeResized(); // can be used instead of SetWindow() when EGLsurface has not been changed + + EGLContext anEglContext = eglGetCurrentContext(); + myView->SetWindow (aWindow, (Aspect_RenderingContext )anEglContext, NULL, NULL); + //saveSnapshot ("/sdcard/Download/tt.png", theWidth, theHeight); +} + +// ======================================================================= +// function : initContent +// purpose : +// ======================================================================= +void OcctJni_Viewer::initContent() +{ + myContext->RemoveAll (Standard_False); + + OSD_Timer aTimer; + aTimer.Start(); + if (!myShape.IsNull()) + { + Handle(AIS_Shape) aShapePrs = new AIS_Shape (myShape); + myContext->Display (aShapePrs, Standard_False); + } + else + { + BRepPrimAPI_MakeBox aBuilder (1.0, 2.0, 3.0); + Handle(AIS_Shape) aShapePrs = new AIS_Shape (aBuilder.Shape()); + myContext->Display (aShapePrs, Standard_False); + } + myView->FitAll(); + + aTimer.Stop(); + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "Presentation computed in " + aTimer.ElapsedTime() + " seconds", Message_Info); +} + +//! Load shape from IGES file +static TopoDS_Shape loadIGES (const TCollection_AsciiString& thePath) +{ + TopoDS_Shape aShape; + IGESControl_Reader aReader; + IFSelect_ReturnStatus aReadStatus = IFSelect_RetFail; + try + { + aReadStatus = aReader.ReadFile (thePath.ToCString()); + } + catch (Standard_Failure) + { + Message::DefaultMessenger()->Send ("Error: IGES reader, computation error", Message_Fail); + return aShape; + } + + if (aReadStatus != IFSelect_RetDone) + { + Message::DefaultMessenger()->Send ("Error: IGES reader, bad file format", Message_Fail); + return aShape; + } + + // now perform the translation + aReader.TransferRoots(); + if (aReader.NbShapes() <= 0) + { + Handle(XSControl_WorkSession) aWorkSession = new XSControl_WorkSession(); + aWorkSession->SelectNorm ("IGES"); + aReader.SetWS (aWorkSession, Standard_True); + aReader.SetReadVisible (Standard_False); + aReader.TransferRoots(); + } + if (aReader.NbShapes() <= 0) + { + Message::DefaultMessenger()->Send ("Error: IGES reader, no shapes has been found", Message_Fail); + return aShape; + } + return aReader.OneShape(); + /*TopoDS_Shape anImportedShape = aReader.OneShape(); + + // apply sewing on the imported shape + BRepBuilderAPI_Sewing aTool (0.0); + aTool.SetNonManifoldMode (Standard_False); + aTool.SetFloatingEdgesMode(Standard_True); + aTool.Load (anImportedShape); + aTool.Perform(); + TopoDS_Shape aSewedShape = aTool.SewedShape(); + + if (aSewedShape.IsNull()) + { + Message::DefaultMessenger()->Send ("Error: Sewing result is empty", Message_Fail); + return aShape; + } + if (aSewedShape.IsSame(anImportedShape)) + { + aShape = anImportedShape; + } + else + { + // apply shape healing + ShapeFix_Shape aShapeFixer(aSewedShape); + aShapeFixer.FixSolidMode() = 1; + aShapeFixer.FixFreeShellMode() = 1; + aShapeFixer.FixFreeFaceMode() = 1; + aShapeFixer.FixFreeWireMode() = 0; + aShapeFixer.FixSameParameterMode() = 0; + aShapeFixer.FixVertexPositionMode() = 0; + aShape = aShapeFixer.Perform() ? aShapeFixer.Shape() : aSewedShape; + } + return aShape;*/ +} + +//! Load shape from STEP file +static TopoDS_Shape loadSTEP (const TCollection_AsciiString& thePath) +{ + STEPControl_Reader aReader; + IFSelect_ReturnStatus aReadStatus = IFSelect_RetFail; + try + { + aReadStatus = aReader.ReadFile (thePath.ToCString()); + } + catch (Standard_Failure) + { + Message::DefaultMessenger()->Send ("Error: STEP reader, computation error", Message_Fail); + return TopoDS_Shape(); + } + + if (aReadStatus != IFSelect_RetDone) + { + Message::DefaultMessenger()->Send ("Error: STEP reader, bad file format", Message_Fail); + return TopoDS_Shape(); + } + else if (aReader.NbRootsForTransfer() <= 0) + { + Message::DefaultMessenger()->Send ("Error: STEP reader, shape is empty", Message_Fail); + return TopoDS_Shape(); + } + + // now perform the translation + aReader.TransferRoots(); + return aReader.OneShape(); +} + +// ======================================================================= +// function : open +// purpose : +// ======================================================================= +bool OcctJni_Viewer::open (const TCollection_AsciiString& thePath) +{ + myShape.Nullify(); + if (!myContext.IsNull()) + { + myContext->RemoveAll (Standard_False); + } + if (thePath.IsEmpty()) + { + return false; + } + + OSD_Timer aTimer; + aTimer.Start(); + TCollection_AsciiString aFormatStr; + const Standard_Integer aLen = thePath.Length(); + if (aLen >= 5 + && thePath.Value (aLen - 4) == '.') + { + aFormatStr = thePath.SubString (aLen - 3, aLen); + } + else if (aLen >= 4 + && thePath.Value (aLen - 3) == '.') + { + aFormatStr = thePath.SubString (aLen - 2, aLen); + } + else if (aLen >= 3 + && thePath.Value (aLen - 2) == '.') + { + aFormatStr = thePath.SubString (aLen - 1, aLen); + } + aFormatStr.LowerCase(); + + TopoDS_Shape aShape; + if (aFormatStr == "stp" + || aFormatStr == "step") + { + aShape = loadSTEP (thePath); + } + else if (aFormatStr == "igs" + || aFormatStr == "iges") + { + aShape = loadIGES (thePath); + } + else + // if (aFormatStr == "brep" + // || aFormatStr == "rle") + { + BRep_Builder aBuilder; + if (!BRepTools::Read (aShape, thePath.ToCString(), aBuilder)) + { + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "Error: file '" + thePath + "' can not be opened!", Message_Info); + return false; + } + } + if (aShape.IsNull()) + { + return false; + } + aTimer.Stop(); + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "File '" + thePath + "' loaded in " + aTimer.ElapsedTime() + " seconds", Message_Info); + + myShape = aShape; + if (myContext.IsNull()) + { + return true; + } + + aTimer.Reset(); + aTimer.Start(); + + Handle(AIS_Shape) aShapePrs = new AIS_Shape (aShape); + myContext->Display (aShapePrs, Standard_False); + myView->FitAll(); + + aTimer.Stop(); + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "Presentation computed in " + aTimer.ElapsedTime() + " seconds", Message_Info); + return true; +} + +// ======================================================================= +// function : saveSnapshot +// purpose : +// ======================================================================= +bool OcctJni_Viewer::saveSnapshot (const TCollection_AsciiString& thePath, + int theWidth, + int theHeight) +{ + if (myContext.IsNull() + || thePath.IsEmpty()) + { + Message::DefaultMessenger()->Send ("Image dump failed - view is unavailable", Message_Fail); + return false; + } + + if (theWidth < 1 + || theHeight < 1) + { + myView->Window()->Size (theWidth, theHeight); + } + if (theWidth < 1 + || theHeight < 1) + { + Message::DefaultMessenger()->Send ("Image dump failed - view is unavailable", Message_Fail); + return false; + } + + Image_AlienPixMap anAlienImage; + if (!anAlienImage.InitTrash (Image_PixMap::ImgBGRA, theWidth, theHeight)) + { + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "RGBA image " + theWidth + "x" + theHeight + " allocation failed", Message_Fail); + return false; + } + + // OpenGL ES does not support fetching data in BGRA format + // while FreeImage does not support RGBA format. + Image_PixMap anImage; + anImage.InitWrapper (Image_PixMap::ImgRGBA, + anAlienImage.ChangeData(), + anAlienImage.SizeX(), + anAlienImage.SizeY(), + anAlienImage.SizeRowBytes()); + if (!myView->ToPixMap (anImage, theWidth, theHeight, Graphic3d_BT_RGBA)) + { + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "View dump to the image " + theWidth + "x" + theHeight + " failed", Message_Fail); + } + + for (Standard_Size aRow = 0; aRow < anAlienImage.SizeY(); ++aRow) + { + for (Standard_Size aCol = 0; aCol < anAlienImage.SizeX(); ++aCol) + { + Image_ColorRGBA& aPixel = anAlienImage.ChangeValue (aRow, aCol); + std::swap (aPixel.r(), aPixel.b()); + //aPixel.a() = 1.0; + } + } + + if (!anAlienImage.Save (thePath)) + { + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "Image saving to path '" + thePath + "' failed", Message_Fail); + return false; + } + Message::DefaultMessenger()->Send (TCollection_AsciiString() + "View " + theWidth + "x" + theHeight + " dumped to image '" + thePath + "'", Message_Info); + return true; +} + +// ======================================================================= +// function : redraw +// purpose : +// ======================================================================= +void OcctJni_Viewer::redraw() +{ + if (myView.IsNull()) + { + return; + } + + myView->Redraw(); +} + +// ======================================================================= +// function : fitAll +// purpose : +// ======================================================================= +void OcctJni_Viewer::fitAll() +{ + if (myView.IsNull()) + { + return; + } + + myView->FitAll(); +} + +// ======================================================================= +// function : startRotation +// purpose : +// ======================================================================= +void OcctJni_Viewer::startRotation (int theStartX, + int theStartY) +{ + if (myView.IsNull()) + { + return; + } + + myView->StartRotation (theStartX, theStartY, 0.45); +} + +// ======================================================================= +// function : onRotation +// purpose : +// ======================================================================= +void OcctJni_Viewer::onRotation (int theX, + int theY) +{ + if (myView.IsNull()) + { + return; + } + + myView->Rotation (theX, theY); +} + +// ======================================================================= +// function : onPanning +// purpose : +// ======================================================================= +void OcctJni_Viewer::onPanning (int theDX, + int theDY) +{ + if (myView.IsNull()) + { + return; + } + + myView->Pan (theDX, theDY); +} + +// ======================================================================= +// function : onClick +// purpose : +// ======================================================================= +void OcctJni_Viewer::onClick (int theX, + int theY) +{ + if (myView.IsNull()) + { + return; + } + + myContext->MoveTo (theX, theY, myView, Standard_False); + myContext->Select (Standard_True); +} + +// ======================================================================= +// function : stopAction +// purpose : +// ======================================================================= +void OcctJni_Viewer::stopAction() +{ + if (myView.IsNull()) + { + return; + } +} + +#define jexp extern "C" JNIEXPORT + +jexp jlong JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppCreate (JNIEnv* theEnv, + jobject theObj) +{ + return jlong(new OcctJni_Viewer()); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppDestroy (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + delete (OcctJni_Viewer* )theCppPtr; + + Handle(Message_Messenger) aMsgMgr = Message::DefaultMessenger(); + aMsgMgr->RemovePrinters (STANDARD_TYPE (OcctJni_MsgPrinter)); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppRelease (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->release(); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppInit (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + Handle(Message_Messenger) aMsgMgr = Message::DefaultMessenger(); + aMsgMgr->RemovePrinters (STANDARD_TYPE (OcctJni_MsgPrinter)); + aMsgMgr->AddPrinter (new OcctJni_MsgPrinter (theEnv, theObj)); + ((OcctJni_Viewer* )theCppPtr)->init(); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppResize (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr, + jint theWidth, + jint theHeight) +{ + ((OcctJni_Viewer* )theCppPtr)->resize (theWidth, theHeight); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppOpen (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr, + jstring thePath) +{ + const char* aPathPtr = theEnv->GetStringUTFChars (thePath, 0); + const TCollection_AsciiString aPath (aPathPtr); + theEnv->ReleaseStringUTFChars (thePath, aPathPtr); + ((OcctJni_Viewer* )theCppPtr)->open (aPath); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppRedraw (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->redraw(); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSetAxoProj (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->setProj (V3d_XposYnegZpos); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSetXposProj (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->setProj (V3d_Xpos); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSetYposProj (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->setProj (V3d_Ypos); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSetZposProj (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->setProj (V3d_Zpos); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSetXnegProj (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->setProj (V3d_Xneg); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSetYnegProj (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->setProj (V3d_Yneg); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSetZnegProj (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->setProj (V3d_Zneg); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppFitAll (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->fitAll(); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppStartRotation (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr, + jint theStartX, + jint theStartY) +{ + ((OcctJni_Viewer* )theCppPtr)->startRotation (theStartX, theStartY); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppOnRotation (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr, + jint theX, + jint theY) +{ + ((OcctJni_Viewer* )theCppPtr)->onRotation (theX, theY); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppOnPanning (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr, + jint theDX, + jint theDY) +{ + ((OcctJni_Viewer* )theCppPtr)->onPanning (theDX, theDY); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppOnClick (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr, + jint theX, + jint theY) +{ + ((OcctJni_Viewer* )theCppPtr)->onClick (theX, theY); +} + +jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppStopAction (JNIEnv* theEnv, + jobject theObj, + jlong theCppPtr) +{ + ((OcctJni_Viewer* )theCppPtr)->stopAction(); +} + +jexp jlong JNICALL Java_com_opencascade_jnisample_OcctJniActivity_cppOcctMajorVersion (JNIEnv* theEnv, + jobject theObj) +{ + return OCC_VERSION_MAJOR; +} + +jexp jlong JNICALL Java_com_opencascade_jnisample_OcctJniActivity_cppOcctMinorVersion (JNIEnv* theEnv, + jobject theObj) +{ + return OCC_VERSION_MINOR; +} + +jexp jlong JNICALL Java_com_opencascade_jnisample_OcctJniActivity_cppOcctMicroVersion (JNIEnv* theEnv, + jobject theObj) +{ + return OCC_VERSION_MAINTENANCE; +} diff --git a/samples/java/jniviewer/jni/OcctJni_Viewer.hxx b/samples/java/jniviewer/jni/OcctJni_Viewer.hxx new file mode 100644 index 0000000000..6bd0a41f3e --- /dev/null +++ b/samples/java/jniviewer/jni/OcctJni_Viewer.hxx @@ -0,0 +1,88 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include + +//! Main C++ back-end for activity. +class OcctJni_Viewer +{ + +public: + + //! Empty constructor + OcctJni_Viewer(); + + //! Initialize the viewer + bool init(); + + //! Release the viewer + void release(); + + //! Resize the viewer + void resize (int theWidth, + int theHeight); + + //! Open CAD file + bool open (const TCollection_AsciiString& thePath); + + //! Take snapshot + bool saveSnapshot (const TCollection_AsciiString& thePath, + int theWidth = 0, + int theHeight = 0); + + //! Viewer update. + void redraw(); + + //! Move camera + void setProj (V3d_TypeOfOrientation theProj) { if (!myView.IsNull()) myView->SetProj (theProj); } + + //! Fit All. + void fitAll(); + + //! Start rotation (remember first point position) + void startRotation (int theStartX, + int theStartY); + + //! Perform rotation (relative to first point) + void onRotation (int theX, + int theY); + + //! Perform panning + void onPanning (int theDX, + int theDY); + + //! Perform selection + void onClick (int theX, + int theY); + + //! Stop previously started action + void stopAction(); + +protected: + + //! Reset viewer content. + void initContent(); + +protected: + + Handle(V3d_Viewer) myViewer; + Handle(V3d_View) myView; + Handle(AIS_InteractiveContext) myContext; + TopoDS_Shape myShape; + +}; diff --git a/samples/java/jniviewer/jni/OcctJni_Window.cxx b/samples/java/jniviewer/jni/OcctJni_Window.cxx new file mode 100644 index 0000000000..8f50abde3b --- /dev/null +++ b/samples/java/jniviewer/jni/OcctJni_Window.cxx @@ -0,0 +1,17 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +IMPLEMENT_STANDARD_HANDLE (OcctJni_Window, Aspect_Window) +IMPLEMENT_STANDARD_RTTIEXT(OcctJni_Window, Aspect_Window) diff --git a/samples/java/jniviewer/jni/OcctJni_Window.hxx b/samples/java/jniviewer/jni/OcctJni_Window.hxx new file mode 100644 index 0000000000..d73c4a6629 --- /dev/null +++ b/samples/java/jniviewer/jni/OcctJni_Window.hxx @@ -0,0 +1,106 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef OcctJni_Window_H +#define OcctJni_Window_H + +#include + +//! This class defines dummy window +class OcctJni_Window : public Aspect_Window +{ + +public: + + //! Creates a wrapper over existing Window handle + OcctJni_Window (const int theWidth, const int theHeight) + : myWidth (theWidth), myHeight(theHeight) {} + + //! Returns native Window handle + virtual Aspect_Drawable NativeHandle() const { return 0; } + + //! Returns parent of native Window handle + virtual Aspect_Drawable NativeParentHandle() const { return 0; } + + virtual void Destroy() {} + + //! Opens the window + virtual void Map() const {} + + //! Closes the window + virtual void Unmap() const {} + + //! Applies the resizing to the window + virtual Aspect_TypeOfResize DoResize() const { return Aspect_TOR_UNKNOWN; } + + //! Apply the mapping change to the window + virtual Standard_Boolean DoMapping() const { return Standard_True; } + + //! Returns True if the window is opened + virtual Standard_Boolean IsMapped() const { return Standard_True; } + + //! Returns The Window RATIO equal to the physical WIDTH/HEIGHT dimensions + virtual Quantity_Ratio Ratio() const { return 1.0; } + + //! Returns The Window POSITION in PIXEL + virtual void Position (Standard_Integer& theX1, + Standard_Integer& theY1, + Standard_Integer& theX2, + Standard_Integer& theY2) const + { + theX1 = 0; + theX2 = myWidth; + theY1 = 0; + theY2 = myHeight; + } + + //! Set The Window POSITION in PIXEL + virtual void SetPosition (const Standard_Integer theX1, + const Standard_Integer theY1, + const Standard_Integer theX2, + const Standard_Integer theY2) + { + myWidth = theX2 - theX1; + myHeight = theY2 - theY1; + } + + //! Returns The Window SIZE in PIXEL + virtual void Size (Standard_Integer& theWidth, + Standard_Integer& theHeight) const + { + theWidth = myWidth; + theHeight = myHeight; + } + + //! Set The Window SIZE in PIXEL + virtual void SetSize (const Standard_Integer theWidth, + const Standard_Integer theHeight) + { + myWidth = theWidth; + myHeight = theHeight; + } + +private: + + int myWidth; + int myHeight; + +public: + + DEFINE_STANDARD_RTTI(OcctJni_Window) + +}; + +DEFINE_STANDARD_HANDLE(OcctJni_Window, Aspect_Window) + +#endif // OcctJni_Window_H diff --git a/samples/java/jniviewer/project.properties b/samples/java/jniviewer/project.properties new file mode 100644 index 0000000000..0840b4a059 --- /dev/null +++ b/samples/java/jniviewer/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-15 diff --git a/samples/java/jniviewer/res/drawable-hdpi/close_l.png b/samples/java/jniviewer/res/drawable-hdpi/close_l.png new file mode 100644 index 0000000000000000000000000000000000000000..0125c4aaf8867b36f628ab01923c6514b3397b0a GIT binary patch literal 1410 zcmV-|1%3L7P)N2bZe?^J zG%heMGmPe!Pyhf007*naR9Hv7nBR*VRTRgoLe*DY+9We`=SR}rCRwvdCX-AunaRv# zerA)+ZklBM14;#bP#6&3WQpr94|AY!5TP^dppp;%fgb_KWH zO^#>QTYU5-w;vb?xnDTkJLjJBJ#%OQJdi1=57OWFk5svFSZ+F{T+6wXs~fYNssUYM z3)gUt%T|18*^cLV%Y%H&f)&=mg|)^bf*GHd+>O0uZv%?nIutt|ENcz6fl0OX7;g_g z=G{%m`kRmsHXv{NkX7qDRKfCQ{c)x3y`^*qcO`#_K{r5j zW8ii3Ywig(M`epgQjL?@PPCK7t0Q8Gl)OHO9e|;`SBgi`OOwo;Zse}ze!#Jb3@t%?#F*XG`X$)4d zORBYJsbuj86}2Rq&P5sDsTAP7O@jIpkXl`~cUNraFUW52r<`PQFv7&b9lK*7TIX{r zVS{9}FDrgDm%<@7gfZ~m1bAzBm#Y}hQ3)Gl)7n#l7{qAvpc8T338b3+@6sh*r;^1Z zsny?Py8~j_19C60GoBsQ;#bqF^(87>Jc6cP7aeCNhU*v@?{6>xVohclwfAg z;z?`OcX-FSAx8a!z!GM&Y)ygG2A>K=hDV*MlwaaIonOQVTOT%j*`K1tL66niyiC@N z5tT7vkTKd~4c%k1va=)ufumbEKHpkJPpEob3TY`82qriev^B-P95 zfvmSaq_V}cw32`LXt_2bmVYy85@L7)IBbm)&PuY;`GLyVaI{!9xVm{!bk^rdz21bn0YC6%y2DAm_U%e^R}3lWoIl33fI z4}xL;!IkSzQyCkCnt4vNqy3~|Nc|K4+S^Vr7iz|PRKfQ9a+A!PY zn(-!;up`@cz9Altsd%SJG{F*z?_76(q|5BcYx-H-?}tO0!I?^$U_aoFBu$h5tUVv7MC@_?Gu#&guV^$0=jgjbPPsA+I%l zqRZ?^SB+fG^v@UUaHnXx^MY>NpO2`i)#^i}s&|x{{(82e-JpN}4~Gu@0}VUi&tq&i Qn*aa+07*qoM6N<$f(pc)N2bZe?^J zG%heMGmPe!Pyhe~t4TybR9HvtmD`IObri=}1uw6>H@}%ocbjC*COesAE}8p1 zm%XIP)<0mWU?0>6TM!C92)_9s_#{4vV4;c%`chEPO8X#Uq4-d!7gQ*gUc|28Zg(?3 z&t%6JUkatR1@nQ!OftVY=X-v?bI#06nKEU{lqq+MeU*|7mwv8ZdnGR|y~jx66)Z0O zw)gY_Wp=*w*lfA{#Oz}2_`arex}dc$(x&+fZQEB_+a7b4NjTlu!CLzQ+mc^m>S8r} z_5QQJQq%U=8iReJd?Ke9Hz>;^$d3sdb-{Xs4dEJg2p`0R_oIY!f<>gewlFL8o|+kplB_#RY+!);BbX-0X+A5gm>Z{ z-taFK73Vw_wGFN&Kb5)u?`AslcE`I@@?-O+%%qL>WWTPrAM=ncaMVDK9ZO1 z&*`@RD{Do&7y`2(A{E4jhrm*CBZ9rdgmb_wFk_t{V>G7h;h1tqg!UjIe+Y@~8Rrim zku_5ERtfL+Z(%FGhT7iulw!Y@Yv~*LhN5TT@0^k(pD4ENZx}n;V*Fl$pbXM-V4*Y^ z*l`MWlL8TFB4`9+*0*_Uc!5(_FQ7*Mvx3-pcfSO|?(ZeuP(@4+JB+BG}!V$m=Bq za8Ac-gzZ5I^bQf+0nw9yJrL3*oDGd?gnx00dy*9m1ubd=rXtIQN;98dXwK%VvP{*r z7m#FrfmQc9Zv5=@BCeW()GBUbnXnSRg_J;`*9wro64oai|jX6mGoa1N(0>Xr`Bf^^A zHm8|CaI*Rls*6W6m$wBiHJ;~X?G)FxF0-b;%Q`Xm>`#0nh(STDJe+bafY`x0p@a2= zatBvvtN#h3$H%CaeK1#RrM>bHIFn65_fHFU?@#QkB($5B#R$YdGcgoFZNpSaCkS>r z1erm?Xx8s^tNj(-P>$_Mx-;lS(FPx%(z5S~}E(_lBcpknnC8&Z5Xe=2X5t`aKTRt;WmNl|qIHY2FWL|fPpxN6Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv000A7NklS}Q%9gD1`dS{5^lY5OHa$Oakh zl><}leYcX)pz=nNdn3D!R@VyC&Kiy8727wW4lwL}({RL-o{Ves(0zOO&5F0t1uPi}>b<)|=K-g;7 zB8&jxnS3*%abS#T1g|&;YCtXS^Uz=xxCB`o!c4hFhXs*^mBpj3u9Sb5Maj#)PP#t z=X5|sNVwZi2ZLvb$c{hI9?3gH6xsOmW)ivld)#T|7g}vS7z8sH z*=%a^&Nx2q=y5IH{Xm_3Wadbl9!uUCM}wN65n=G`;Fz#DN8TC7;TClfHP)X~Cry3( zTm&}(VzC?=)C6A=T98{!_=ltcC~r*Ps^ERO?)r YzrD-?<2F(YPXGV_07*qoM6N<$f`H1phyVZp literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-hdpi/ic_launcher.png b/samples/java/jniviewer/res/drawable-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..d27ba82c09f3fa1357a7501c50058a10524fdc8a GIT binary patch literal 2591 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 literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-hdpi/message.png b/samples/java/jniviewer/res/drawable-hdpi/message.png new file mode 100644 index 0000000000000000000000000000000000000000..a3dc8cc2092e136070180accd996700d34e35318 GIT binary patch literal 443 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL8%hgh?3y^w370~qEv=}#LT=BJwMkFg)(D3 zQ$0figD*u3fyQcix;TbJxF<`nE=~~XX`0A)WbYI>Sd{IUQ0|p z0=pX1@|?;T1aAnxQVD5zc;L%2(*Vsbh6`(^8W?vTIbyY;u)tbiM%g{4mybgZqt5QXN^3W|R9$wCywb2RUrmvv;hWp3V^evY*}axr zyDzFW$71R=35m12FK`tH8J%o+5Xc_XN2bPDNB8 zb~7$DE-^4L^m3s900R+8L_t(oN6nbcYZE~jhm%;XwHPn?Xg1l3P{krukRalro~pHn z9=&Aqonj3s>9$SUrj5mqwzjr{e}DxA{{z8;dKJWrDBe^Q8U(3c6pA11`kQnXvy%bS z*q!VPPj+~dclX(k*_n(@)3lw!&dL2Z++>3>oGAHlR3w5GZL=XF1af>QH~?C~Zm z3xYsw{Hr%TcZ%-9+^9#eJ)~|*$>Lp4dfq{IV_wuJIIPLa$S2>xOjJr0&+HH_RLL0i z3X-cRN5<9z(foIYZ=75$9HC0a;0el+Q7~2}K+)TJo`v__zj93bC{;2BD~Mb7#vjWE zeTjmE-s%@cAew8&@tYVNRf7Kb(yFV7|fh83h$p^T)>L+b&)0z!`lg=Ng5g z{`AKtrVWC!qJD(nc_}&7OJu8x1cDK(A7+FXpi-%<4kSvXnB{I@(ph?`XXX)B02(;00000NkvXXu0mjfxi+FH literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-hdpi/open_l.png b/samples/java/jniviewer/res/drawable-hdpi/open_l.png new file mode 100644 index 0000000000000000000000000000000000000000..6069ce38931d2f2a277d74bef58c431ff9d7e0dc GIT binary patch literal 1415 zcmV;21$g?2P)N2bZe?^J zG%heMGmPe!Pyhf01xZ9fR9Hu~n9GY>RUF5wf{(2ZZIXNMx#!%ZGfgsPlHANPb`VanAX@P7eH6iVf|W($e2VP2J9|HXmX?^QEF2v}$_uiWBda%+a`;N~^+Te864z7wiqFJlLf2XhKm8>D3Q^6KeX1N%$bOjQ5cp!Er+&86Bf? za+JzWvM;sF(@eq#q27L0GMqnf1Ua4UI@zQc(hT*y3sSAU#$U@H=!sCu6RP_ z5O!_HyOP>_fl2rvwz?-}JGg_w5fviXbp*~K+J&}x;ZU`1Fc}|+VgHDsX$T=WhMZPl z*T@R5AF8(|ODp9^m~8P7z4sAu2NZ=})H5^h4x>v4i-CSC$zrS3FNJZQJHP@)W!eom_ zs_HswjXxt6IE5V}C?;|;g6YJ)me-o^G1=lNRNMK2I(QdZTeB>{?m3Q!kkjr=(o}v! zLVmTqbVy5I7u@MgiZjPh!QufWj!m)J{GG`bPrjz>x$fvY!QXU z4?@FuUbHsuib3Xh3c0)i6)c%!qkoA>_<%I~3*tfL%BmOC>uf+EUi7Ej(O+Z|KH#2z zLxE_<;21;6B=E|RP4|M-P-{%Gcu=$ToMOQklsSc!H3`K__oR-&w_GZ^ai-{|GaSGX z!;oJJ;eM^~d!epXnPl;ZYpP57&J_YpL56Wxf!25!rjTOSyT~MbfI;9>*$(${cBgQ} z*{of(lU=MD$C+gD2v4ddQ8&Mqy%Yj!9qt#7*dO0j`oTM-u0O$Ki$`d5PDp+47LLZ0 zq)@3c=T3SZx2!)DM1;8eNk~_<+0K4GckrV6>HBiQ#Zx z=(?{7HSJ+0SvAeMXY(uZSHgM%#162;X96KH!G>;j-!PufX`Kz!3}36a;^A6?bfvN%&Ay+q-2a zq-6-EB9utcgXN4rLCxOdOu~m{twXav*sAbWS`MI6dFy+UnVyu^I*&35A68V2mU|X0 zTLE2m(mAoxi$cde!pwXrf%8(^n3a0&MbdR^xq63RrTRAtHT6OGc;NoG|I2{`{{YWU V;LmX$iEaP@002ovPDHLkV1mJ`uTB5} literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-hdpi/open_p.png b/samples/java/jniviewer/res/drawable-hdpi/open_p.png new file mode 100644 index 0000000000000000000000000000000000000000..c0898e6d6a4835fc8614573dccdb71b564f8aa5c GIT binary patch literal 1322 zcmV+_1=aeAP)N2bZe?^J zG%heMGmPe!Pyhe~s7XXYR9HvtmCK7;bri=(g{oT}W|G|3@7|;{O)_SZ+}z|oa-X@+ zAU5HpHE(-O53dNzdVn=YMGjosU zX6|08t?eQoI3)b?`<>tSJbou*BSwrEF=9l@v?#%p)PbVd-X;z4gKYVMLJ+01sPRHs z>in239a2K^Wz~SH=|b7^U`p@p6UF?764lLY%YB0u)z?|sxKh&W-D$ly)Hq`}P?0sr zwqO8l?VxP9P<01T_UB>Rn}?D+f52P)YeeaN!MD2SNke`m+kVd>_09(0(%vGn^MhdZ z_k`XYlzK5t_XbcwjWbRNRozL74e9#uKuNVPO<7Sg<<3LVTLi&B4t%%<#9slyU4)Vo zCq*OJCW`YLD|IhWjm{?Zlvo-bzDrQ`_Onb&dygpgPlE36l>k0V-IBg1-(-unHu^!~Oyg%m8Zz z3Db3M(X##(-BLcGXGJ^Ha7UnzlXR#1C8Jrtv!3a(FteS<*rxgf>6oIRd2b44bd&AH zNggv&h!%lK1L$Kl@HgUZ}S7OjVZI zS#6mU4TV>FX9Oeoi?{oD9xLj_z^1-Z-|=za;^o5taE*0i%=X|`$q2Uu)p?(j^b>4D zsb_NknPXzx7F6RS+V*c!ZnRH(gMHSUOStqx53_~ug-iR?#h|e51S~V}tN?8++|DWS zx06!zT0!i;!?xx=<<-T@g0*tF=&tSZxQ|WS)Wgd9OPJwh;IPVExCm^p0E`zWj2-S2 zyY|;*Nj{y)xpVR@MbCBg4N9?JWxMWo#ENdN)Eg{9#c4yO%g z2HULaTx44M8LB47g-WZEU3u@|>++LC)HaHWb4f6~tAZ2l@#&jL+Y8%Z*bYO($B#Cg zWcV;y{EuOW(=5d5csHoFevuK?7noXmHgo;|L)E1!)6q_Gx_yy%{67RQOavdNRM1Bo zU@$RS`d}lVEEjT$adSeHPfylc%ahf*mbtnAFb%QBb>tU`*1gQ@#vX5(*dq1;ZQIvr z)BL%hb+1gcrE}x6^;6@O%H!kHn(=R$gkpn`_ix#-+K5YFR$~b;O7f gBSwt)uZ)fT4YDub&rYOub^rhX07*qoM6N<$f_wIrhX4Qo literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-hdpi/proj_back.png b/samples/java/jniviewer/res/drawable-hdpi/proj_back.png new file mode 100755 index 0000000000000000000000000000000000000000..77ab199ab5decb6fb87e47ab3ca62b3f51f5c57a GIT binary patch literal 1716 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 FV1mREeRco< literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-hdpi/proj_front.png b/samples/java/jniviewer/res/drawable-hdpi/proj_front.png new file mode 100755 index 0000000000000000000000000000000000000000..b93d4d3e099ad3cb90324f88ffb2cd4be2494735 GIT binary patch literal 1604 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 N002ovPDHLkV1hP3QUd@0 literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-hdpi/proj_right.png b/samples/java/jniviewer/res/drawable-hdpi/proj_right.png new file mode 100755 index 0000000000000000000000000000000000000000..8cdb338b4ce8e40aefdb016c0bfd3ba57fee6855 GIT binary patch literal 1644 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 RU6ud<002ovPDHLkV1gkYd;tIe literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-mdpi/ic_launcher.png b/samples/java/jniviewer/res/drawable-mdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..4b86dbf5e952b98811db056f7fe155288f8ff320 GIT binary patch literal 1609 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 literal 0 HcmV?d00001 diff --git a/samples/java/jniviewer/res/drawable-xxhdpi/ic_launcher.png b/samples/java/jniviewer/res/drawable-xxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..a34301f3864675a2b09308ce64ebd482130141bc GIT binary patch literal 5560 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/java/jniviewer/res/values/id.xml b/samples/java/jniviewer/res/values/id.xml new file mode 100644 index 0000000000..56bde943a6 --- /dev/null +++ b/samples/java/jniviewer/res/values/id.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/samples/java/jniviewer/res/values/strings.xml b/samples/java/jniviewer/res/values/strings.xml new file mode 100644 index 0000000000..6f2a431604 --- /dev/null +++ b/samples/java/jniviewer/res/values/strings.xml @@ -0,0 +1,35 @@ + + + OpenCASCADE JNI Java Sample + #484848 + #0099CC + #66252525 + + .png + .jpg + + + .brep + .rle + .iges + .igs + .step + .stp + + wireframe/shading + color + material + transparency + show/hide hidden lines + + OpenCASCADE JNI Java Sample

+

Simple viewer for BREP, STEP and IGES files.

+

Driven by Open CASCADE Technology %d.%d.%d.

+

Copyright 2014 OPEN CASCADE SAS.

+

+

http://www.opencascade.com

+

http://www.opencascade.org

+ ]]> +
+
diff --git a/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniActivity.java b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniActivity.java new file mode 100644 index 0000000000..7cb5366496 --- /dev/null +++ b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniActivity.java @@ -0,0 +1,778 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +package com.opencascade.jnisample; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; + +import android.app.Activity; +import android.content.Context; + +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; + +import android.text.Html; +import android.text.Html.ImageGetter; +import android.text.Spanned; +import android.util.TypedValue; +import android.view.Display; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.view.ViewGroup.LayoutParams; +import android.widget.TextView; +import android.widget.Toast; + +//! Main activity +public class OcctJniActivity extends Activity implements OnClickListener +{ + + //! Auxiliary method to print temporary info messages + public static void printShortInfo (Activity theActivity, + CharSequence theInfo) + { + Context aCtx = theActivity.getApplicationContext(); + Toast aToast = Toast.makeText (aCtx, theInfo, Toast.LENGTH_LONG); + aToast.show(); + } + + //! Load single native library + private static boolean loadLibVerbose (String theLibName, + StringBuilder theLoadedInfo, + StringBuilder theFailedInfo) + { + try + { + System.loadLibrary (theLibName); + theLoadedInfo.append ("Info: native library \""); + theLoadedInfo.append (theLibName); + theLoadedInfo.append ("\" has been loaded\n"); + return true; + } + catch (java.lang.UnsatisfiedLinkError theError) + { + theFailedInfo.append ("Error: native library \""); + theFailedInfo.append (theLibName); + theFailedInfo.append ("\" is unavailable:\n " + theError.getMessage()); + return false; + } + catch (SecurityException theError) + { + theFailedInfo.append ("Error: native library \""); + theFailedInfo.append (theLibName); + theFailedInfo.append ("\" can not be loaded for security reasons:\n " + theError.getMessage()); + return false; + } + } + + public static boolean wasNativesLoadCalled = false; + public static boolean areNativeLoaded = false; + public static String nativeLoaded = ""; + public static String nativeFailed = ""; + + //! Auxiliary method to load native libraries + public boolean loadNatives() + { + if (wasNativesLoadCalled) + { + return areNativeLoaded; + } + wasNativesLoadCalled = true; + StringBuilder aLoaded = new StringBuilder(); + StringBuilder aFailed = new StringBuilder(); + + // copy OCCT resources + String aResFolder = getFilesDir().getAbsolutePath(); + copyAssetFolder (getAssets(), "Shaders", aResFolder + "/Shaders"); + copyAssetFolder (getAssets(), "SHMessage", aResFolder + "/SHMessage"); + copyAssetFolder (getAssets(), "XSMessage", aResFolder + "/XSMessage"); + copyAssetFolder (getAssets(), "TObj", aResFolder + "/TObj"); + copyAssetFolder (getAssets(), "UnitsAPI", aResFolder + "/UnitsAPI"); + + // C++ runtime + loadLibVerbose ("gnustl_shared", aLoaded, aFailed); + + // 3rd-parties + loadLibVerbose ("freetype", aLoaded, aFailed); + loadLibVerbose ("freeimage", aLoaded, aFailed); + + if (// OCCT modeling + !loadLibVerbose ("TKernel", aLoaded, aFailed) + || !loadLibVerbose ("TKMath", aLoaded, aFailed) + || !loadLibVerbose ("TKG2d", aLoaded, aFailed) + || !loadLibVerbose ("TKG3d", aLoaded, aFailed) + || !loadLibVerbose ("TKGeomBase", aLoaded, aFailed) + || !loadLibVerbose ("TKBRep", aLoaded, aFailed) + || !loadLibVerbose ("TKGeomAlgo", aLoaded, aFailed) + || !loadLibVerbose ("TKTopAlgo", aLoaded, aFailed) + || !loadLibVerbose ("TKShHealing", aLoaded, aFailed) + || !loadLibVerbose ("TKMesh", aLoaded, aFailed) + // exchange + || !loadLibVerbose ("TKPrim", aLoaded, aFailed) + || !loadLibVerbose ("TKBO", aLoaded, aFailed) + || !loadLibVerbose ("TKBool", aLoaded, aFailed) + || !loadLibVerbose ("TKFillet", aLoaded, aFailed) + || !loadLibVerbose ("TKOffset", aLoaded, aFailed) + || !loadLibVerbose ("TKXSBase", aLoaded, aFailed) + || !loadLibVerbose ("TKIGES", aLoaded, aFailed) + || !loadLibVerbose ("TKSTEPBase", aLoaded, aFailed) + || !loadLibVerbose ("TKSTEPAttr", aLoaded, aFailed) + || !loadLibVerbose ("TKSTEP209", aLoaded, aFailed) + || !loadLibVerbose ("TKSTEP", aLoaded, aFailed) + // OCCT Visualization + || !loadLibVerbose ("TKService", aLoaded, aFailed) + || !loadLibVerbose ("TKHLR", aLoaded, aFailed) + || !loadLibVerbose ("TKV3d", aLoaded, aFailed) + || !loadLibVerbose ("TKOpenGl", aLoaded, aFailed) + // application code + || !loadLibVerbose ("TKJniSample", aLoaded, aFailed)) + { + nativeLoaded = aLoaded.toString(); + nativeFailed = aFailed.toString(); + areNativeLoaded = false; + //exitWithError (theActivity, "Broken apk?\n" + theFailedInfo); + return false; + } + nativeLoaded = aLoaded.toString(); + areNativeLoaded = true; + return true; + } + + //! Create activity + @Override protected void onCreate (Bundle theBundle) + { + super.onCreate (theBundle); + + boolean isLoaded = loadNatives(); + if (!isLoaded) + { + printShortInfo (this, nativeFailed); + OcctJniLogger.postMessage (nativeLoaded + "\n" + nativeFailed); + } + + setContentView (R.layout.activity_main); + + myOcctView = (OcctJniView )findViewById (R.id.custom_view); + myMessageTextView = (TextView )findViewById (R.id.message_view); + OcctJniLogger.setTextView (myMessageTextView); + + createViewAndButtons (Configuration.ORIENTATION_LANDSCAPE); + + myButtonPreferSize = defineButtonSize ((LinearLayout )findViewById (R.id.panel_menu)); + ImageButton aScrollBtn = (ImageButton )findViewById (R.id.scroll_btn); + aScrollBtn.setY (myButtonPreferSize); + aScrollBtn.setOnTouchListener (new View.OnTouchListener() + { + @Override + public boolean onTouch (View theView, MotionEvent theEvent) + { + return onScrollBtnTouch (theView, theEvent); + } + }); + + onConfigurationChanged (getResources().getConfiguration()); + + Intent anIntent = getIntent(); + Uri aDataUrl = anIntent != null ? anIntent.getData() : null; + String aDataPath = aDataUrl != null ? aDataUrl.getPath() : ""; + myOcctView.open (aDataPath); + myLastPath = aDataPath; + } + + //! Handle scroll events + private boolean onScrollBtnTouch (View theView, + MotionEvent theEvent) + { + switch (theEvent.getAction()) + { + case MotionEvent.ACTION_DOWN: + { + LinearLayout aPanelMenu = (LinearLayout )findViewById (R.id.panel_menu); + boolean isLandscape = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); + if (aPanelMenu.getVisibility() == View.VISIBLE) + { + aPanelMenu.setVisibility (View.GONE); + if (!isLandscape) + { + ((ImageButton )theView).setImageResource (R.drawable.open_p); + theView.setY (0); + } + else + { + ((ImageButton )theView).setImageResource (R.drawable.open_l); + theView.setX (0); + } + } + else + { + aPanelMenu.setVisibility (View.VISIBLE); + if (!isLandscape) + { + ((ImageButton )theView).setImageResource (R.drawable.close_p); + theView.setY (myButtonPreferSize); + } + else + { + ((ImageButton )theView).setImageResource (R.drawable.close_l); + theView.setX (myButtonPreferSize); + } + } + break; + } + } + return false; + } + + //! Initialize views and buttons + private void createViewAndButtons (int theOrientation) + { + // open button + ImageButton anOpenButton = (ImageButton )findViewById (R.id.open); + anOpenButton.setOnClickListener (this); + + // fit all + ImageButton aFitAllButton = (ImageButton )findViewById (R.id.fit); + aFitAllButton.setOnClickListener (this); + aFitAllButton.setOnTouchListener (new View.OnTouchListener() + { + @Override + public boolean onTouch (View theView, MotionEvent theEvent) + { + return onTouchButton (theView, theEvent); + } + }); + + // message + ImageButton aMessageButton = (ImageButton )findViewById (R.id.message); + aMessageButton.setOnClickListener (this); + + // info + ImageButton anInfoButton = (ImageButton )findViewById (R.id.info); + anInfoButton.setOnClickListener (this); + + // font for text view + TextView anInfoView = (TextView )findViewById (R.id.info_view); + anInfoView.setTextSize (TypedValue.COMPLEX_UNIT_SP, 18); + + // add submenu buttons + createSubmenuBtn (R.id.view, R.id.view_group, + Arrays.asList (R.id.proj_front, R.id.proj_top, R.id.proj_left, + R.id.proj_back, R.id.proj_bottom, R.id.proj_right), + Arrays.asList (R.drawable.proj_front, R.drawable.proj_top, R.drawable.proj_left, + R.drawable.proj_back, R.drawable.proj_bottom, R.drawable.proj_right), + 4); + } + + @Override protected void onNewIntent (Intent theIntent) + { + super.onNewIntent (theIntent); + setIntent (theIntent); + } + + @Override protected void onDestroy() + { + super.onDestroy(); + OcctJniLogger.setTextView (null); + } + + @Override protected void onPause() + { + super.onPause(); + myOcctView.onPause(); + } + + @Override protected void onResume() + { + super.onResume(); + myOcctView.onResume(); + + Intent anIntent = getIntent(); + Uri aDataUrl = anIntent != null ? anIntent.getData() : null; + String aDataPath = aDataUrl != null ? aDataUrl.getPath() : ""; + if (!aDataPath.equals (myLastPath)) + { + myOcctView.open (aDataPath); + myLastPath = aDataPath; + } + } + + //! Copy folder from assets + private boolean copyAssetFolder (AssetManager theAssetMgr, + String theAssetFolder, + String theFolderPathTo) + { + try + { + String[] aFiles = theAssetMgr.list (theAssetFolder); + File aFolder = new File (theFolderPathTo); + aFolder.mkdirs(); + boolean isOk = true; + for (String aFileIter : aFiles) + { + if (aFileIter.contains (".")) + { + isOk &= copyAsset (theAssetMgr, + theAssetFolder + "/" + aFileIter, + theFolderPathTo + "/" + aFileIter); + } + else + { + isOk &= copyAssetFolder (theAssetMgr, + theAssetFolder + "/" + aFileIter, + theFolderPathTo + "/" + aFileIter); + } + } + return isOk; + } + catch (Exception theError) + { + theError.printStackTrace(); + return false; + } + } + + //! Copy single file from assets + private boolean copyAsset (AssetManager theAssetMgr, + String thePathFrom, + String thePathTo) + { + try + { + InputStream aStreamIn = theAssetMgr.open (thePathFrom); + File aFileTo = new File (thePathTo); + aFileTo.createNewFile(); + OutputStream aStreamOut = new FileOutputStream (thePathTo); + copyStreamContent (aStreamIn, aStreamOut); + aStreamIn.close(); + aStreamIn = null; + aStreamOut.flush(); + aStreamOut.close(); + aStreamOut = null; + return true; + } + catch (Exception theError) + { + theError.printStackTrace(); + return false; + } + } + + //! Copy single file + private static void copyStreamContent (InputStream theIn, + OutputStream theOut) throws IOException + { + byte[] aBuffer = new byte[1024]; + int aNbReadBytes = 0; + while ((aNbReadBytes = theIn.read (aBuffer)) != -1) + { + theOut.write (aBuffer, 0, aNbReadBytes); + } + } + + //! Show/hide text view + private void switchTextView (TextView theTextView, + ImageButton theClickedBtn, + boolean theToSwitchOn) + { + if (theTextView != null + && theTextView.getVisibility() == View.GONE + && theToSwitchOn) + { + theTextView.setVisibility (View.VISIBLE); + theClickedBtn.setBackgroundColor (getResources().getColor(R.color.pressedBtnColor)); + setTextViewPosition (theTextView); + } + else + { + theTextView.setVisibility (View.GONE); + theClickedBtn.setBackgroundColor (getResources().getColor (R.color.btnColor)); + } + } + + //! Setup text view position + private void setTextViewPosition (TextView theTextView) + { + if (theTextView.getVisibility() != View.VISIBLE) + { + return; + } + + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) + { + theTextView.setX (myButtonPreferSize); + theTextView.setY (0); + } + else + { + theTextView.setX (0); + theTextView.setY (myButtonPreferSize); + } + } + + @Override + public void onClick (View theButton) + { + ImageButton aClickedBtn = (ImageButton )theButton; + switch (aClickedBtn.getId()) + { + case R.id.message: + { + switchTextView ((TextView )findViewById (R.id.info_view), + (ImageButton )findViewById (R.id.info), false); + switchTextView (myMessageTextView, aClickedBtn, true); + return; + } + case R.id.info: + { + String aText = getString (R.string.info_html); + aText = String.format (aText, cppOcctMajorVersion(), cppOcctMinorVersion(), cppOcctMicroVersion()); + Spanned aSpanned = Html.fromHtml (aText, new ImageGetter() + { + @Override + public Drawable getDrawable (String theSource) + { + Resources aResources = getResources(); + int anId = aResources.getIdentifier (theSource, "drawable", getPackageName()); + Drawable aRes = aResources.getDrawable (anId); + aRes.setBounds (0, 0, aRes.getIntrinsicWidth(), aRes.getIntrinsicHeight()); + return aRes; + } + }, null); + + TextView anInfoView = (TextView )findViewById (R.id.info_view); + anInfoView.setText (aSpanned); + switchTextView (myMessageTextView, (ImageButton ) findViewById (R.id.message), false); + switchTextView (anInfoView, aClickedBtn, true); + return; + } + case R.id.fit: + { + myOcctView.fitAll(); + return; + } + case R.id.proj_front: + { + myOcctView.setProj (OcctJniRenderer.TypeOfOrientation.Xpos); + return; + } + case R.id.proj_left: + { + myOcctView.setProj (OcctJniRenderer.TypeOfOrientation.Yneg); + return; + } + case R.id.proj_top: + { + myOcctView.setProj (OcctJniRenderer.TypeOfOrientation.Zpos); + return; + } + case R.id.proj_back: + { + myOcctView.setProj (OcctJniRenderer.TypeOfOrientation.Xneg); + return; + } + case R.id.proj_right: + { + myOcctView.setProj (OcctJniRenderer.TypeOfOrientation.Ypos); + return; + } + case R.id.proj_bottom: + { + myOcctView.setProj (OcctJniRenderer.TypeOfOrientation.Zneg); + return; + } + case R.id.open: + { + File aPath = Environment.getExternalStorageDirectory(); + aClickedBtn.setBackgroundColor (getResources().getColor(R.color.pressedBtnColor)); + if (myFileOpenDialog == null) + { + myFileOpenDialog = new OcctJniFileDialog (this, aPath); + myFileOpenDialog.setFileEndsWith (".brep"); + myFileOpenDialog.setFileEndsWith (".rle"); + myFileOpenDialog.setFileEndsWith (".iges"); + myFileOpenDialog.setFileEndsWith (".igs"); + myFileOpenDialog.setFileEndsWith (".step"); + myFileOpenDialog.setFileEndsWith (".stp"); + myFileOpenDialog.addFileListener (new OcctJniFileDialog.FileSelectedListener() + { + public void fileSelected (File theFile) + { + if (theFile != null && myOcctView != null) + { + myOcctView.open (theFile.getPath()); + } + } + }); + myFileOpenDialog.addDialogDismissedListener (new OcctJniFileDialog.DialogDismissedListener() + { + @Override + public void dialogDismissed() + { + ImageButton openButton = (ImageButton )findViewById (R.id.open); + openButton.setBackgroundColor (getResources().getColor(R.color.btnColor)); + } + }); + } + myFileOpenDialog.showDialog(); + return; + } + } + } + + private void createSubmenuBtn (int theParentBtnId, + int theParentLayoutId, + final List theNewButtonIds, + final List theNewButtonImageIds, + int thePosition) + { + int aPosInList = 0; + final ImageButton aParentBtn = (ImageButton )findViewById (theParentBtnId); + + ViewGroup.LayoutParams aParams = null; + LinearLayout parentLayout = (LinearLayout ) findViewById (theParentLayoutId); + for (Integer newButtonId : theNewButtonIds) + { + ImageButton aNewButton = (ImageButton )findViewById (newButtonId); + if (aNewButton == null) + { + aNewButton = (ImageButton )new ImageButton (this); + aNewButton.setId (newButtonId); + aNewButton.setImageResource (theNewButtonImageIds.get (aPosInList)); + aNewButton.setLayoutParams (aParams); + parentLayout.addView (aNewButton); + } + + aNewButton.setOnClickListener (this); + aNewButton.setVisibility (View.GONE); + + aNewButton.setOnTouchListener (new View.OnTouchListener() + { + @Override + public boolean onTouch (View theView, MotionEvent theEvent) + { + return onTouchButton (theView, theEvent); + } + }); + ++aPosInList; + } + + if (aParentBtn != null) + { + aParentBtn.setOnTouchListener (null); + aParentBtn.setOnTouchListener (new View.OnTouchListener() + { + @Override + public boolean onTouch (View theView, MotionEvent theEvent) + { + if (theEvent.getAction () == MotionEvent.ACTION_DOWN) + { + Boolean isVisible = false; + for (Integer aNewButtonId : theNewButtonIds) + { + ImageButton anBtn = (ImageButton )findViewById (aNewButtonId); + if (anBtn != null) + { + if (anBtn.getVisibility() == View.GONE) + { + anBtn.setVisibility (View.VISIBLE); + isVisible = true; + } + else + { + anBtn.setVisibility (View.GONE); + } + } + } + aParentBtn.setBackgroundColor (!isVisible ? getResources().getColor(R.color.btnColor) : getResources().getColor(R.color.pressedBtnColor)); + } + return false; + } + }); + } + } + + //! Implements onTouch functionality + private boolean onTouchButton (View theView, + MotionEvent theEvent) + { + switch (theEvent.getAction()) + { + case MotionEvent.ACTION_DOWN: + ((ImageButton )theView).setBackgroundColor (getResources().getColor (R.color.pressedBtnColor)); + break; + case MotionEvent.ACTION_UP: + ((ImageButton )theView).setBackgroundColor (getResources().getColor (R.color.btnColor)); + break; + } + return false; + } + + //! Handle configuration change event + @Override + public void onConfigurationChanged (Configuration theNewConfig) + { + super.onConfigurationChanged (theNewConfig); + LinearLayout aLayoutPanelMenu = (LinearLayout )findViewById (R.id.panel_menu); + LayoutParams aPanelMenuLayoutParams = aLayoutPanelMenu.getLayoutParams(); + + LinearLayout aLayoutViewGroup = (LinearLayout )findViewById (R.id.view_group); + LayoutParams aViewGroupLayoutParams = aLayoutViewGroup.getLayoutParams(); + ImageButton aScrollBtn = (ImageButton )findViewById (R.id.scroll_btn); + LayoutParams aScrollBtnLayoutParams = aScrollBtn.getLayoutParams(); + + myButtonPreferSize = defineButtonSize ((LinearLayout )findViewById (R.id.panel_menu)); + defineButtonSize ((LinearLayout )findViewById (R.id.view_group)); + + switch (theNewConfig.orientation) + { + case Configuration.ORIENTATION_PORTRAIT: + { + setHorizontal (aLayoutPanelMenu, aPanelMenuLayoutParams); + setHorizontal (aLayoutViewGroup, aViewGroupLayoutParams); + aLayoutViewGroup.setGravity (Gravity.BOTTOM); + + aScrollBtnLayoutParams.height = LayoutParams.WRAP_CONTENT; + aScrollBtnLayoutParams.width = LayoutParams.MATCH_PARENT; + aScrollBtn.setLayoutParams (aScrollBtnLayoutParams); + if (aLayoutPanelMenu.getVisibility() == View.VISIBLE) + { + aScrollBtn.setImageResource (R.drawable.close_p); + aScrollBtn.setY (myButtonPreferSize); + aScrollBtn.setX (0); + } + else + { + aScrollBtn.setImageResource (R.drawable.open_p); + aScrollBtn.setY (0); + aScrollBtn.setX (0); + } + break; + } + case Configuration.ORIENTATION_LANDSCAPE: + { + setVertical (aLayoutPanelMenu, aPanelMenuLayoutParams); + setVertical (aLayoutViewGroup, aViewGroupLayoutParams); + aLayoutViewGroup.setGravity (Gravity.RIGHT); + + aScrollBtnLayoutParams.height = LayoutParams.MATCH_PARENT; + aScrollBtnLayoutParams.width = LayoutParams.WRAP_CONTENT; + aScrollBtn.setLayoutParams (aScrollBtnLayoutParams); + if (aLayoutPanelMenu.getVisibility() == View.VISIBLE) + { + aScrollBtn.setImageResource (R.drawable.close_l); + aScrollBtn.setX (myButtonPreferSize); + aScrollBtn.setY (0); + } + else + { + aScrollBtn.setImageResource (R.drawable.open_l); + aScrollBtn.setY (0); + aScrollBtn.setX (0); + } + break; + } + } + setTextViewPosition (myMessageTextView); + setTextViewPosition ((TextView )findViewById (R.id.info_view)); + } + + private void setHorizontal (LinearLayout theLayout, + LayoutParams theLayoutParams) + { + theLayout.setOrientation (LinearLayout.HORIZONTAL); + theLayoutParams.height = LayoutParams.WRAP_CONTENT; + theLayoutParams.width = LayoutParams.MATCH_PARENT; + theLayout.setLayoutParams (theLayoutParams); + } + + private void setVertical (LinearLayout theLayout, + LayoutParams theLayoutParams) + { + theLayout.setOrientation (LinearLayout.VERTICAL); + theLayoutParams.height = LayoutParams.MATCH_PARENT; + theLayoutParams.width = LayoutParams.WRAP_CONTENT; + theLayout.setLayoutParams (theLayoutParams); + } + + //! Define button size + private int defineButtonSize (LinearLayout theLayout) + { + boolean isLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + Display aDisplay = getWindowManager().getDefaultDisplay(); + Point aDispPnt = new Point(); + aDisplay.getSize (aDispPnt); + + int aNbChildren = theLayout.getChildCount(); + int aHeight = aDispPnt.y / aNbChildren; + int aWidth = aDispPnt.x / aNbChildren; + int aResultSize = 0; + for (int aChildIter = 0; aChildIter < aNbChildren; ++aChildIter) + { + View aView = theLayout.getChildAt (aChildIter); + if (aView instanceof ImageButton) + { + ImageButton aButton = (ImageButton )aView; + if (isLandscape) + { + aButton.setMinimumWidth (aHeight); + } + else + { + aButton.setMinimumHeight (aWidth); + } + } + } + if (isLandscape) + { + aResultSize = aHeight; + } + else + { + aResultSize = aWidth; + } + return aResultSize; + } + + //! OCCT major version + private native long cppOcctMajorVersion(); + + //! OCCT minor version + private native long cppOcctMinorVersion(); + + //! OCCT micro version + private native long cppOcctMicroVersion(); + + private OcctJniView myOcctView; + private TextView myMessageTextView; + private String myLastPath; + private OcctJniFileDialog myFileOpenDialog; + private int myButtonPreferSize = 65; + +} diff --git a/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniFileDialog.java b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniFileDialog.java new file mode 100644 index 0000000000..e12cc1aa2b --- /dev/null +++ b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniFileDialog.java @@ -0,0 +1,376 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +package com.opencascade.jnisample; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.opencascade.jnisample.ListenerList.FireHandler; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.Environment; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.Spinner; + +//! Simple open file dialog +public class OcctJniFileDialog +{ + + public enum DialogMode + { + FileOpen, FileExport, FileSave + } + + private static final String PARENT_DIR = ".."; + private String[] myFileList; + private File myCurrentPath; + private DialogMode myDialogMode = DialogMode.FileOpen; + + private ListenerList myFileListenerList = new ListenerList(); + private ListenerList myDialogDismissedList = new ListenerList(); + private final Activity myActivity; + private List myFileEndsWith; + private EditText myFileNameInput; + private Spinner myFileExtSpinner; + int myCurrentExtPositionInList = 0; + + public interface FileSelectedListener + { + void fileSelected (File theFile); + } + + public interface DialogDismissedListener + { + void dialogDismissed(); + } + + //! Main constructor. + public OcctJniFileDialog (Activity theActivity, + File thePath) + { + myActivity = theActivity; + if (!thePath.exists()) + { + thePath = Environment.getExternalStorageDirectory(); + } + loadFileList (thePath); + } + + //! Create new dialog + public Dialog createFileDialog() + { + final Object[] anObjWrapper = new Object[1]; + Dialog aDialog = null; + AlertDialog.Builder aBuilder = new AlertDialog.Builder (myActivity); + + aBuilder.setTitle (myCurrentPath.getPath()); + LinearLayout aTitleLayout = new LinearLayout (myActivity); + aTitleLayout.setLayoutParams (new LinearLayout.LayoutParams (LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + aTitleLayout.setOrientation (LinearLayout.VERTICAL); + + ListView list = new ListView (myActivity); + list.setScrollingCacheEnabled(false); + list.setBackgroundColor (Color.parseColor ("#33B5E5")); + + list.setAdapter (new ArrayAdapter (myActivity, android.R.layout.select_dialog_item, myFileList)); + list.setOnItemClickListener (new AdapterView.OnItemClickListener () + { + + public void onItemClick (AdapterView arg0, View view, int pos, long id) + { + String fileChosen = myFileList[pos]; + File aChosenFile = getChosenFile (fileChosen); + if (aChosenFile.isDirectory()) + { + loadFileList (aChosenFile); + ((Dialog )anObjWrapper[0]).cancel(); + ((Dialog )anObjWrapper[0]).dismiss(); + showDialog(); + } + else + { + if (myDialogMode == DialogMode.FileOpen) + { + ((Dialog )anObjWrapper[0]).cancel(); + ((Dialog )anObjWrapper[0]).dismiss(); + fireFileSelectedEvent (aChosenFile); + } + else + { + myFileNameInput.setText (aChosenFile.getName()); + } + } + } + }); + list.setLayoutParams (new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, 0.6f)); + aTitleLayout.addView (list); + + if (myDialogMode == DialogMode.FileSave + || myDialogMode == DialogMode.FileExport) + { + myFileNameInput = new EditText (myActivity); + myFileExtSpinner = new Spinner (myActivity); + ArrayAdapter adapter = null; + if (myDialogMode == DialogMode.FileExport) + { + adapter = ArrayAdapter.createFromResource (myActivity, R.array.ext_to_exp, + android.R.layout.simple_spinner_item); + } + else + { + adapter = ArrayAdapter.createFromResource (myActivity, R.array.ext_to_save, + android.R.layout.simple_spinner_item); + } + // Specify the layout to use when the list of choices appears + adapter.setDropDownViewResource (android.R.layout.simple_spinner_dropdown_item); + // Apply the adapter to the spinner + myFileExtSpinner.setAdapter (adapter); + myFileExtSpinner.setSelection (myCurrentExtPositionInList); + + myFileExtSpinner.setOnItemSelectedListener (new AdapterView.OnItemSelectedListener() + { + + @Override + public void onNothingSelected (AdapterView theParentView) + { + // your code here + } + + @Override + public void onItemSelected (AdapterView theParent, View theView, int thePosition, long theId) + { + if (myCurrentExtPositionInList != thePosition) + { + myCurrentExtPositionInList = thePosition; + setFileEndsWith (Arrays.asList (myFileExtSpinner.getSelectedItem().toString())); + loadFileList (myCurrentPath); + ((Dialog )anObjWrapper[0]).cancel(); + ((Dialog )anObjWrapper[0]).dismiss(); + showDialog(); + } + } + }); + + myFileExtSpinner.setLayoutParams (new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT, 0.2f)); + // titleLayout.addView(fileExtSpinner); + myFileNameInput.setLayoutParams (new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT, 0.2f)); + LinearLayout aControlsView = new LinearLayout (myActivity); + + aControlsView.addView (myFileNameInput); + aControlsView.addView (myFileExtSpinner); + + aTitleLayout.addView (aControlsView); + aBuilder.setView (aTitleLayout); + aBuilder.setPositiveButton ("OK", new DialogInterface.OnClickListener() + { + @Override + public void onClick (DialogInterface theDialog, int theWhich) + { + if (theWhich >= 0) + { + String aFileChosen = myFileList[theWhich]; + File aChosenFile = getChosenFile (aFileChosen); + fireFileSelectedEvent (aChosenFile); + } + } + }).setNegativeButton ("Cancel", null); + } + else + { + aBuilder.setNegativeButton ("Cancel", null); + } + + aBuilder.setView (aTitleLayout); + + aDialog = aBuilder.show(); + aDialog.setOnDismissListener (new DialogInterface.OnDismissListener() + { + @Override + public void onDismiss (DialogInterface theDialog) + { + fireDialogDismissedEvent(); + } + }); + anObjWrapper[0] = aDialog; + return aDialog; + } + + public void addFileListener (FileSelectedListener theListener) + { + myFileListenerList.add (theListener); + } + + public void addDialogDismissedListener (DialogDismissedListener theListener) + { + myDialogDismissedList.add (theListener); + } + + //! Show file dialog + public void showDialog() + { + createFileDialog().show(); + } + + private void fireFileSelectedEvent (final File theFile) + { + myFileListenerList.fireEvent (new FireHandler() + { + public void fireEvent (FileSelectedListener theListener) + { + theListener.fileSelected (theFile); + } + }); + } + + private void fireDialogDismissedEvent() + { + myDialogDismissedList.fireEvent (new FireHandler() + { + public void fireEvent (DialogDismissedListener theListener) + { + theListener.dialogDismissed(); + } + }); + } + + private void loadFileList (File thePath) + { + myCurrentPath = thePath; + List aList = new ArrayList(); + if (thePath.exists()) + { + if (thePath.getParentFile() != null) + { + aList.add (PARENT_DIR); + } + FilenameFilter aFilter = new FilenameFilter() + { + public boolean accept (File theDir, String theFilename) + { + File aSel = new File (theDir, theFilename); + if (!aSel.canRead()) + { + return false; + } + boolean isEndWith = false; + if (myFileEndsWith != null) + { + for (String aFileExtIter : myFileEndsWith) + { + if (theFilename.toLowerCase().endsWith (aFileExtIter)) + { + isEndWith = true; + break; + } + } + } + return isEndWith || aSel.isDirectory(); + } + }; + String[] aFileList1 = thePath.list (aFilter); + if (aFileList1 != null) + { + for (String aFileIter : aFileList1) + { + aList.add (aFileIter); + } + } + } + myFileList = (String[] )aList.toArray (new String[] {}); + } + + private File getChosenFile (String theFileChosen) + { + if (theFileChosen.equals (PARENT_DIR)) + return myCurrentPath.getParentFile(); + else + return new File (myCurrentPath, theFileChosen); + } + + public void setFileEndsWith (String fileEndsWith) + { + if (myFileEndsWith == null) + { + myFileEndsWith = new ArrayList(); + } + if (myFileEndsWith.indexOf (fileEndsWith) == -1) + { + myFileEndsWith.add (fileEndsWith); + } + } + + public void setFileEndsWith (List theFileEndsWith) + { + myFileEndsWith = theFileEndsWith; + } + + public DialogMode DialogMode() + { + return myDialogMode; + } + + public void DialogMode (DialogMode theMode) + { + myDialogMode = theMode; + } +} + +class ListenerList +{ + private List myListenerList = new ArrayList(); + + public interface FireHandler + { + void fireEvent (L theListener); + } + + public void add (L theListener) + { + myListenerList.add (theListener); + } + + public void fireEvent (FireHandler theFireHandler) + { + List aCopy = new ArrayList (myListenerList); + for (L anIter : aCopy) + { + theFireHandler.fireEvent (anIter); + } + } + + public void remove (L theListener) + { + myListenerList.remove (theListener); + } + + public List getListenerList() + { + return myListenerList; + } +} diff --git a/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniLogger.java b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniLogger.java new file mode 100644 index 0000000000..cc8b1e4942 --- /dev/null +++ b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniLogger.java @@ -0,0 +1,71 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +package com.opencascade.jnisample; + +import java.util.concurrent.locks.ReentrantLock; + +import android.util.Log; +import android.widget.TextView; + +//! Auxiliary class for logging messages +public class OcctJniLogger +{ + + //! Setup text view + public static void setTextView (TextView theTextView) + { + if (myTextView != null) + { + myLog = myTextView.getText().toString(); + } + + myTextView = theTextView; + if (myTextView != null) + { + myTextView.setText (myLog); + myLog = ""; + } + } + + //! Interface implementation + public static void postMessage (String theText) + { + final String aCopy = new String (theText); + Log.e (myTag, theText); + + myMutex.lock(); + final TextView aView = myTextView; + if (aView == null) + { + myLog += aCopy; + myMutex.unlock(); + return; + } + + aView.post (new Runnable() + { + public void run() + { + aView.setText (aView.getText() + aCopy + "\n"); + } + }); + myMutex.unlock(); + } + + private static final String myTag = "occtJniViewer"; + private static final ReentrantLock myMutex = new ReentrantLock (true); + private static TextView myTextView = null; + private static String myLog = ""; + +} diff --git a/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniRenderer.java b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniRenderer.java new file mode 100644 index 0000000000..731037a9ca --- /dev/null +++ b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniRenderer.java @@ -0,0 +1,218 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +package com.opencascade.jnisample; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import android.opengl.GLSurfaceView; + +//! Wrapper for C++ OCCT viewer. +public class OcctJniRenderer implements GLSurfaceView.Renderer +{ + + //! Wrapper for V3d_TypeOfOrientation + enum TypeOfOrientation + { + Xpos, // front + Ypos, // left + Zpos, // top + Xneg, // back + Yneg, // right + Zneg // bottom + }; + + //! Empty constructor. + OcctJniRenderer() + { + if (OcctJniActivity.areNativeLoaded) + { + myCppViewer = cppCreate(); + } + } + + //! Open file. + public void open (String thePath) + { + if (myCppViewer != 0) + { + cppOpen (myCppViewer, thePath); + } + } + + //! Update viewer. + public void onDrawFrame (GL10 theGl) + { + if (myCppViewer != 0) + { + cppRedraw (myCppViewer); + } + } + + //! (re)initialize viewer. + public void onSurfaceChanged (GL10 theGl, int theWidth, int theHeight) + { + if (myCppViewer != 0) + { + cppResize (myCppViewer, theWidth, theHeight); + } + } + + public void onSurfaceCreated (GL10 theGl, EGLConfig theEglConfig) + { + if (myCppViewer != 0) + { + cppInit (myCppViewer); + } + } + + //! Initialize rotation (remember first point position) + public void onStartRotation (int theStartX, int theStartY) + { + if (myCppViewer != 0) + { + cppStartRotation (myCppViewer, theStartX, theStartY); + } + } + + //! Perform rotation (relative to first point) + public void onRotation (int theX, int theY) + { + if (myCppViewer != 0) + { + cppOnRotation (myCppViewer, theX, theY); + } + } + + //! Perform panning + public void onPanning (int theDX, int theDY) + { + if (myCppViewer != 0) + { + cppOnPanning (myCppViewer, theDX, theDY); + } + } + + //! Perform selection + public void onClick (int theX, int theY) + { + if (myCppViewer != 0) + { + cppOnClick (myCppViewer, theX, theY); + } + } + + //! Stop previously active action (e.g. discard first rotation point) + public void onStopAction() + { + if (myCppViewer != 0) + { + cppStopAction (myCppViewer); + } + } + + //! Fit All + public void fitAll() + { + if (myCppViewer != 0) + { + cppFitAll (myCppViewer); + } + } + + //! Move camera + public void setProj (TypeOfOrientation theProj) + { + if (myCppViewer == 0) + { + return; + } + + switch (theProj) + { + case Xpos: cppSetXposProj (myCppViewer); break; + case Ypos: cppSetYposProj (myCppViewer); break; + case Zpos: cppSetZposProj (myCppViewer); break; + case Xneg: cppSetXnegProj (myCppViewer); break; + case Yneg: cppSetYnegProj (myCppViewer); break; + case Zneg: cppSetZnegProj (myCppViewer); break; + } + } + + //! Post message to the text view. + public void postMessage (String theText) + { + OcctJniLogger.postMessage (theText); + } + + //! Create instance of C++ class + private native long cppCreate(); + + //! Destroy instance of C++ class + private native void cppDestroy (long theCppPtr); + + //! Initialize OCCT viewer (steal OpenGL ES context bound to this thread) + private native void cppInit (long theCppPtr); + + //! Resize OCCT viewer + private native void cppResize (long theCppPtr, int theWidth, int theHeight); + + //! Open CAD file + private native void cppOpen (long theCppPtr, String thePath); + + //! Handle detection in the viewer + private native void cppMoveTo (long theCppPtr, int theX, int theY); + + //! Redraw OCCT viewer + private native void cppRedraw (long theCppPtr); + + //! Fit All + private native void cppFitAll (long theCppPtr); + + //! Move camera + private native void cppSetXposProj (long theCppPtr); + + //! Move camera + private native void cppSetYposProj (long theCppPtr); + + //! Move camera + private native void cppSetZposProj (long theCppPtr); + + //! Move camera + private native void cppSetXnegProj (long theCppPtr); + + //! Move camera + private native void cppSetYnegProj (long theCppPtr); + + //! Move camera + private native void cppSetZnegProj (long theCppPtr); + + //! Initialize rotation + private native void cppStartRotation (long theCppPtr, int theStartX, int theStartY); + + //! Perform rotation + private native void cppOnRotation (long theCppPtr, int theX, int theY); + + //! Perform panning + private native void cppOnPanning (long theCppPtr, int theDX, int theDY); + + //! Perform selection + private native void cppOnClick (long theCppPtr, int theX, int theY); + + //! Stop action (rotation / panning / scaling) + private native void cppStopAction (long theCppPtr); + + private long myCppViewer = 0; //!< pointer to c++ class instance + +} diff --git a/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniView.java b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniView.java new file mode 100644 index 0000000000..7909d9c61d --- /dev/null +++ b/samples/java/jniviewer/src/com/opencascade/jnisample/OcctJniView.java @@ -0,0 +1,332 @@ +// Copyright (c) 2014 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +package com.opencascade.jnisample; + +import android.app.ActionBar.LayoutParams; +import android.content.Context; +import android.graphics.PointF; +import android.opengl.GLSurfaceView; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.MotionEvent; +import android.widget.RelativeLayout; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +//! OpenGL ES 2.0+ view. +//! Performs rendering in parallel thread. +class OcctJniView extends GLSurfaceView +{ + + // ! Default constructor. + public OcctJniView (Context theContext, + AttributeSet theAttrs) + { + super (theContext, theAttrs); + + setPreserveEGLContextOnPause (true); + setEGLContextFactory (new ContextFactory()); + setEGLConfigChooser (new ConfigChooser()); + + RelativeLayout.LayoutParams aLParams = new RelativeLayout.LayoutParams (LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + aLParams.addRule (RelativeLayout.ALIGN_TOP); + + myRenderer = new OcctJniRenderer(); + setRenderer (myRenderer); + } + + //! Open file. + public void open (String thePath) + { + final String aPath = thePath; + queueEvent (new Runnable() { public void run() { myRenderer.open (aPath); }}); + } + + //! Create OpenGL ES 2.0+ context + private static class ContextFactory implements GLSurfaceView.EGLContextFactory + { + private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + public EGLContext createContext (EGL10 theEgl, + EGLDisplay theEglDisplay, + EGLConfig theEglConfig) + { + if (theEglConfig == null) + { + return null; + } + + // reset EGL errors stack + int anError = EGL10.EGL_SUCCESS; + while ((anError = theEgl.eglGetError()) != EGL10.EGL_SUCCESS) {} + + int[] anAttribs = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; + EGLContext aEglContext = theEgl.eglCreateContext (theEglDisplay, theEglConfig, EGL10.EGL_NO_CONTEXT, anAttribs); + + while ((anError = theEgl.eglGetError()) != EGL10.EGL_SUCCESS) + { + OcctJniLogger.postMessage ("Error: eglCreateContext() " + String.format ("0x%x", anError)); + } + return aEglContext; + } + + public void destroyContext (EGL10 theEgl, + EGLDisplay theEglDisplay, + EGLContext theEglContext) + { + theEgl.eglDestroyContext (theEglDisplay, theEglContext); + } + } + + //! Search for RGB24 config with depth and stencil buffers + private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser + { + //! Reset EGL errors stack + private void popEglErrors (EGL10 theEgl) + { + int anError = EGL10.EGL_SUCCESS; + while ((anError = theEgl.eglGetError()) != EGL10.EGL_SUCCESS) + { + OcctJniLogger.postMessage ("EGL Error: " + String.format ("0x%x", anError)); + } + } + + //! Auxiliary method to dump EGL configuration - for debugging purposes + @SuppressWarnings("unused") + private void printConfig (EGL10 theEgl, + EGLDisplay theEglDisplay, + EGLConfig theEglConfig) + { + int[] THE_ATTRIBS = + { + EGL10.EGL_BUFFER_SIZE, EGL10.EGL_ALPHA_SIZE, EGL10.EGL_BLUE_SIZE, EGL10.EGL_GREEN_SIZE, EGL10.EGL_RED_SIZE, EGL10.EGL_DEPTH_SIZE, EGL10.EGL_STENCIL_SIZE, + EGL10.EGL_CONFIG_CAVEAT, + EGL10.EGL_CONFIG_ID, + EGL10.EGL_LEVEL, + EGL10.EGL_MAX_PBUFFER_HEIGHT, EGL10.EGL_MAX_PBUFFER_PIXELS, EGL10.EGL_MAX_PBUFFER_WIDTH, + EGL10.EGL_NATIVE_RENDERABLE, EGL10.EGL_NATIVE_VISUAL_ID, EGL10.EGL_NATIVE_VISUAL_TYPE, + 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, + EGL10.EGL_SAMPLES, EGL10.EGL_SAMPLE_BUFFERS, + EGL10.EGL_SURFACE_TYPE, + EGL10.EGL_TRANSPARENT_TYPE, EGL10.EGL_TRANSPARENT_RED_VALUE, EGL10.EGL_TRANSPARENT_GREEN_VALUE, EGL10.EGL_TRANSPARENT_BLUE_VALUE, + 0x3039, 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGB, EGL10.EGL_BIND_TO_TEXTURE_RGBA, + 0x303B, 0x303C, // EGL10.EGL_MIN_SWAP_INTERVAL, EGL10.EGL_MAX_SWAP_INTERVAL + EGL10.EGL_LUMINANCE_SIZE, EGL10.EGL_ALPHA_MASK_SIZE, + EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RENDERABLE_TYPE, + 0x3042 // EGL10.EGL_CONFORMANT + }; + String[] THE_NAMES = + { + "EGL_BUFFER_SIZE", "EGL_ALPHA_SIZE", "EGL_BLUE_SIZE", "EGL_GREEN_SIZE", "EGL_RED_SIZE", "EGL_DEPTH_SIZE", "EGL_STENCIL_SIZE", + "EGL_CONFIG_CAVEAT", + "EGL_CONFIG_ID", + "EGL_LEVEL", + "EGL_MAX_PBUFFER_HEIGHT", "EGL_MAX_PBUFFER_PIXELS", "EGL_MAX_PBUFFER_WIDTH", + "EGL_NATIVE_RENDERABLE", "EGL_NATIVE_VISUAL_ID", "EGL_NATIVE_VISUAL_TYPE", + "EGL_PRESERVED_RESOURCES", + "EGL_SAMPLES", "EGL_SAMPLE_BUFFERS", + "EGL_SURFACE_TYPE", + "EGL_TRANSPARENT_TYPE", "EGL_TRANSPARENT_RED_VALUE", "EGL_TRANSPARENT_GREEN_VALUE", "EGL_TRANSPARENT_BLUE_VALUE", + "EGL_BIND_TO_TEXTURE_RGB", "EGL_BIND_TO_TEXTURE_RGBA", + "EGL_MIN_SWAP_INTERVAL", "EGL_MAX_SWAP_INTERVAL", + "EGL_LUMINANCE_SIZE", "EGL_ALPHA_MASK_SIZE", + "EGL_COLOR_BUFFER_TYPE", "EGL_RENDERABLE_TYPE", + "EGL_CONFORMANT" + }; + int[] aValue = new int[1]; + for (int anAttrIter = 0; anAttrIter < THE_ATTRIBS.length; ++anAttrIter) + { + int anAttr = THE_ATTRIBS[anAttrIter]; + String aName = THE_NAMES [anAttrIter]; + if (theEgl.eglGetConfigAttrib (theEglDisplay, theEglConfig, anAttr, aValue)) + { + OcctJniLogger.postMessage (String.format (" %s: %d\n", aName, aValue[0])); + } + else + { + popEglErrors (theEgl); + } + } + } + + //! Interface implementation + public EGLConfig chooseConfig (EGL10 theEgl, + EGLDisplay theEglDisplay) + { + int EGL_OPENGL_ES2_BIT = 4; + int[] aCfgAttribs = + { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 0, + EGL10.EGL_DEPTH_SIZE, 24, + EGL10.EGL_STENCIL_SIZE, 8, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE + }; + + EGLConfig aConfigs[] = new EGLConfig[1]; + int[] aNbConfigs = new int[1]; + if (!theEgl.eglChooseConfig (theEglDisplay, aCfgAttribs, aConfigs, 1, aNbConfigs) + || aConfigs[0] == null) + { + aCfgAttribs[4 * 2 + 1] = 16; // try config with smaller depth buffer + popEglErrors (theEgl); + if (!theEgl.eglChooseConfig (theEglDisplay, aCfgAttribs, aConfigs, 1, aNbConfigs) + || aConfigs[0] == null) + { + OcctJniLogger.postMessage ("Error: eglChooseConfig() has failed!"); + return null; + } + } + + //printConfig (theEgl, theEglDisplay, aConfigs[0]); + return aConfigs[0]; + } + } + + //! Callback to handle touch events + @Override public boolean onTouchEvent (MotionEvent theEvent) + { + int aPointerIndex = theEvent.getActionIndex(); + int aPointerId = theEvent.getPointerId (aPointerIndex); + int aMaskedAction = theEvent.getActionMasked(); + switch (aMaskedAction) + { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + { + PointF aPntLast = null; + if (myActivePointers.size() >= 1) + { + aPntLast = myActivePointers.get (myActivePointers.keyAt (0)); + } + + final PointF aPnt = new PointF(); + aPnt.x = theEvent.getX (aPointerIndex); + aPnt.y = theEvent.getY (aPointerIndex); + myActivePointers.put (aPointerId, aPnt); + + switch (myActivePointers.size()) + { + case 1: + { + final int aStartX = (int )aPnt.x; + final int aStartY = (int )aPnt.y; + queueEvent (new Runnable() { public void run() { myRenderer.onStartRotation (aStartX, aStartY); }}); + break; + } + case 2: + { + myPanFrom.x = (aPntLast.x + aPnt.x) * 0.5f; + myPanFrom.y = (aPntLast.y + aPnt.y) * 0.5f; + break; + } + } + + break; + } + case MotionEvent.ACTION_MOVE: + { + for (int aNbPointers = theEvent.getPointerCount(), aPntIter = 0; aPntIter < aNbPointers; ++aPntIter) + { + PointF aPnt = myActivePointers.get (theEvent.getPointerId (aPntIter)); + if (aPnt != null) + { + aPnt.x = theEvent.getX (aPntIter); + aPnt.y = theEvent.getY (aPntIter); + } + } + + switch (myActivePointers.size()) + { + case 1: + { + PointF aPnt = myActivePointers.get (theEvent.getPointerId (0)); + final int anX = (int )aPnt.x; + final int anY = (int )aPnt.y; + queueEvent (new Runnable() { public void run() { myRenderer.onRotation (anX, anY); }}); + break; + } + case 2: + { + PointF aPnt1 = myActivePointers.get (myActivePointers.keyAt (0)); + PointF aPnt2 = myActivePointers.get (myActivePointers.keyAt (1)); + PointF aPntAver = new PointF ((aPnt1.x + aPnt2.x) * 0.5f, + (aPnt1.y + aPnt2.y) * 0.5f); + final int aDX = (int )(aPntAver.x - myPanFrom.x); + final int aDY = (int )(myPanFrom.y -aPntAver.y); + myPanFrom.x = aPntAver.x; + myPanFrom.y = aPntAver.y; + queueEvent (new Runnable() { public void run() { myRenderer.onPanning (aDX, aDY); }}); + } + } + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + { + myActivePointers.remove (aPointerId); + if (myActivePointers.size() == 0) + { + final int aPressX = (int )theEvent.getX (aPointerIndex); + final int aPressY = (int )theEvent.getY (aPointerIndex); + double aPressTimeMs = theEvent.getEventTime() - theEvent.getDownTime(); + if (aPressTimeMs < 100.0) + { + queueEvent (new Runnable() { public void run() { myRenderer.onClick (aPressX, aPressY); }}); + break; + } + } + else if (myActivePointers.size() == 1) + { + PointF aPnt = myActivePointers.get (myActivePointers.keyAt (0)); + final int aStartX = (int )aPnt.x; + final int aStartY = (int )aPnt.y; + queueEvent (new Runnable() { public void run() { myRenderer.onStartRotation (aStartX, aStartY); }}); + } + //queueEvent (new Runnable() { public void run() { myRenderer.onStopAction(); }}); + break; + } + } + ///invalidate(); + return true; + } + + //! Fit All + public void fitAll() + { + queueEvent (new Runnable() { public void run() { myRenderer.fitAll(); }}); + } + + //! Move camera + public void setProj (final OcctJniRenderer.TypeOfOrientation theProj) + { + queueEvent (new Runnable() { public void run() { myRenderer.setProj (theProj); }}); + } + + //! OCCT viewer + private OcctJniRenderer myRenderer = null; + + //! Touch events cache + private SparseArray myActivePointers = new SparseArray(); + + //! Starting point for panning event + private PointF myPanFrom = new PointF (0.0f, 0.0f); + +}