diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..ebeb4aa
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,54 @@
+.gitattributes eol=lf
+.gitignore eol=lf
+*.gradle eol=lf
+*.txt eol=lf
+*.h eol=lf
+*.c eol=lf
+*.cpp eol=lf
+*.cxx eol=lf
+*.hxx eol=lf
+*.lxx eol=lf
+*.pxx eol=lf
+*.cl eol=lf
+*.mm eol=lf
+*.i eol=lf
+*.el eol=lf
+*.sh eol=lf
+*.csh eol=lf
+*.tcl eol=lf
+*.cbp eol=lf
+*.svg eol=lf
+*.xib eol=lf
+*.plist eol=lf
+*.java eol=lf
+*.igs eol=lf
+*.iges eol=lf
+*.stp eol=lf
+*.step eol=lf
+*.brep eol=lf
+*.md eol=lf
+*.fs eol=lf
+*.vs eol=lf
+*.glsl eol=lf
+*.bat eol=crlf
+*.cmd eol=crlf
+*.rc eol=crlf
+*.cs eol=crlf
+*.def eol=crlf
+*.ini eol=crlf
+*.so binary
+*.dylib binary
+*.7z binary
+*.pdf binary
+*.png binary
+*.jpg binary
+*.bmp binary
+*.gif binary
+*.xwd binary
+*.ico binary
+*.icns binary
+*.std binary
+*.gz binary
+*.doc binary
+*.mft binary
+*.stl binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3328b0f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+/.gradle
+/.idea
+/build
+/gradle
+gradlew
+gradlew.bat
+/app/.cxx
+/app/build
+gradle.properties
+local.properties
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..16c16e4
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+Copyright (c) 2021 OPEN CASCADE SAS
+
+This project is part of the examples of the Open CASCADE Technology software library.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+include required OCCT headers
diff --git a/ReadMe.md b/ReadMe.md
new file mode 100644
index 0000000..b4946c7
--- /dev/null
+++ b/ReadMe.md
@@ -0,0 +1,39 @@
+OCCT JniViewer Kotlin sample for Android
+==================
+
+This sample demonstrates simple way of using OCCT libraries in Android application written using Kotlin.
+The project has been converted from original Java JNI sample.
+
+The connection between Kotlin and OCCT (C++) level is provided by proxy library, libTKJniSample.so, written in C++ with exported JNI methods of Kotlin 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 Kotlin using manually created proxy library.
+
+Install Android Studio 4.0+ and building tools (check Tools -> SDK Manager):
+- Android SDK (API level 21 or higher).
+- Android SDK build tools.
+- Android NDK r16 or higher (coming with CMake toolchain).
+ Using NDK r18 or newer will require changing ANDROID_STL in project settings.
+- CMake 3.10+.
+
+Specify this folder location in Android Studio for opening project.
+You might need re-entering Android SDK explicitly in File -> Project Structure -> SDK Location settings (SDK, NDK, JDK locations).
+
+This sample expects OCCT to be already build - please refer to appropriate CMake building instructions in OCCT documentation.
+The following variables should be added into file gradle.properties (see gradle.properties.template as template):
+- `OCCT_ROOT` - path to OCCT installation folder.
+- `FREETYPE_ROOT` - path to FreeType installation folder.
+
+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, and application must package this library as well as extra component ("gnustl_shared" by default - see also `ANDROID_STL`).
+
+After successful build via Build -> Rebuild Project, the application can be packaged to Android:
+- Deploy and run application on connected device or emulator directly from Android Studio using adb interface by menu items "Run" and "Debug". This would sign package with debug certificate.
+- Prepare signed end-user package using wizard Build -> Generate signed APK.
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..277b8b4
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,53 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "30.0.0"
+
+ defaultConfig {
+ applicationId "com.opencascade.jnisample"
+ minSdkVersion 21
+ targetSdkVersion 26
+
+ ndk {
+ abiFilters "arm64-v8a"
+ }
+
+ externalNativeBuild {
+ cmake {
+ arguments "-DOCCT_ROOT=" + OCCT_ROOT,
+ "-DFREETYPE_ROOT=" + FREETYPE_ROOT,
+ "-DANDROID_STL=gnustl_shared"
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+
+ sourceSets {
+ main {
+ manifest.srcFile 'src/main/AndroidManifest.xml'
+ assets.srcDirs = [OCCT_ROOT + "/src"]
+ }
+ }
+
+ externalNativeBuild {
+ cmake {
+ path "src/main/jni/CMakeLists.txt"
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'java/com/opencascade/jnisample', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
+repositories {
+ mavenCentral()
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c25a6d6
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/opencascade/jnisample/OcctJniActivity.kt b/app/src/main/java/com/opencascade/jnisample/OcctJniActivity.kt
new file mode 100644
index 0000000..714c187
--- /dev/null
+++ b/app/src/main/java/com/opencascade/jnisample/OcctJniActivity.kt
@@ -0,0 +1,692 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+package com.opencascade.jnisample
+
+import android.Manifest
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.AssetManager
+import android.content.res.Configuration
+import android.graphics.Point
+import android.os.Bundle
+import android.os.Environment
+import android.text.Html
+import android.text.Html.ImageGetter
+import android.util.TypedValue
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.LinearLayout
+import android.widget.TextView
+import android.widget.Toast
+import java.io.*
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.util.*
+import kotlin.jvm.Throws
+
+//! Main activity
+class OcctJniActivity : Activity(), View.OnClickListener {
+ //! Auxiliary method to load native libraries
+ fun loadNatives(): Boolean {
+ if (wasNativesLoadCalled) {
+ return areNativeLoaded
+ }
+ wasNativesLoadCalled = true
+ val aLoaded = StringBuilder()
+ val aFailed = StringBuilder()
+
+ // copy OCCT resources
+ val aResFolder = filesDir.absolutePath
+ copyAssetFolder(assets, "src/SHMessage", "$aResFolder/SHMessage")
+ copyAssetFolder(assets, "src/XSMessage", "$aResFolder/XSMessage")
+
+ // 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("TKSTL", 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("TKOpenGles", 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 fun onCreate(theBundle: Bundle?) {
+ super.onCreate(theBundle)
+ val isLoaded = loadNatives()
+ if (!isLoaded) {
+ printShortInfo(this, nativeFailed)
+ OcctJniLogger.postMessage("""
+ $nativeLoaded
+ $nativeFailed
+ """.trimIndent())
+ }
+ setContentView(R.layout.activity_main)
+ myOcctView = findViewById(R.id.custom_view) as OcctJniView
+ myMessageTextView = findViewById(R.id.message_view) as TextView
+ OcctJniLogger.setTextView(myMessageTextView)
+ createViewAndButtons(Configuration.ORIENTATION_LANDSCAPE)
+ myButtonPreferSize = defineButtonSize(findViewById(R.id.panel_menu) as LinearLayout)
+ val aScrollBtn = findViewById(R.id.scroll_btn) as ImageButton
+ aScrollBtn.y = myButtonPreferSize.toFloat()
+ aScrollBtn.setOnTouchListener { theView, theEvent -> onScrollBtnTouch(theView, theEvent) }
+ onConfigurationChanged(resources.configuration)
+ val anIntent = intent
+ val aDataUrl = anIntent?.data
+ val aDataPath = if (aDataUrl != null) aDataUrl.path else ""
+ myOcctView!!.open(aDataPath)
+ myLastPath = aDataPath
+ myContext = ContextWrapper(this)
+ myContext!!.getExternalFilesDir(null)
+ }
+
+ //! Handle scroll events
+ private fun onScrollBtnTouch(theView: View,
+ theEvent: MotionEvent): Boolean {
+ when (theEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ val aPanelMenu = findViewById(R.id.panel_menu) as LinearLayout
+ val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ if (aPanelMenu.visibility == View.VISIBLE) {
+ aPanelMenu.visibility = View.GONE
+ if (!isLandscape) {
+ (theView as ImageButton).setImageResource(R.drawable.open_p)
+ theView.setY(0f)
+ } else {
+ (theView as ImageButton).setImageResource(R.drawable.open_l)
+ theView.setX(0f)
+ }
+ } else {
+ aPanelMenu.visibility = View.VISIBLE
+ if (!isLandscape) {
+ (theView as ImageButton).setImageResource(R.drawable.close_p)
+ theView.setY(myButtonPreferSize.toFloat())
+ } else {
+ (theView as ImageButton).setImageResource(R.drawable.close_l)
+ theView.setX(myButtonPreferSize.toFloat())
+ }
+ }
+ }
+ }
+ return false
+ }
+
+ //! Initialize views and buttons
+ @Suppress("UNUSED_PARAMETER")
+ private fun createViewAndButtons(theOrientation: Int) {
+ // open button
+ val anOpenButton = findViewById(R.id.open) as ImageButton
+ anOpenButton.setOnClickListener(this)
+
+ // fit all
+ val aFitAllButton = findViewById(R.id.fit) as ImageButton
+ aFitAllButton.setOnClickListener(this)
+ aFitAllButton.setOnTouchListener { theView, theEvent -> onTouchButton(theView, theEvent) }
+
+ // message
+ val aMessageButton = findViewById(R.id.message) as ImageButton
+ aMessageButton.setOnClickListener(this)
+
+ // info
+ val anInfoButton = findViewById(R.id.info) as ImageButton
+ anInfoButton.setOnClickListener(this)
+
+ // font for text view
+ val anInfoView = findViewById(R.id.info_view) as TextView
+ anInfoView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
+
+ // 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 fun onNewIntent(theIntent: Intent) {
+ super.onNewIntent(theIntent)
+ intent = theIntent
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ OcctJniLogger.setTextView(null)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ myOcctView!!.onPause()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ myOcctView!!.onResume()
+ val anIntent = intent
+ val aDataUrl = anIntent?.data
+ val aDataPath = if (aDataUrl != null) aDataUrl.path else ""
+ if (aDataPath != myLastPath) {
+ myOcctView!!.open(aDataPath)
+ myLastPath = aDataPath
+ }
+ }
+
+ //! Copy folder from assets
+ private fun copyAssetFolder(theAssetMgr: AssetManager,
+ theAssetFolder: String,
+ theFolderPathTo: String): Boolean {
+ return try {
+ val aFiles = theAssetMgr.list(theAssetFolder)
+ val aFolder = File(theFolderPathTo)
+ aFolder.mkdirs()
+ var isOk = true
+ for (aFileIter in aFiles) {
+ isOk = if (aFileIter.contains(".")) {
+ isOk and copyAsset(theAssetMgr,
+ "$theAssetFolder/$aFileIter",
+ "$theFolderPathTo/$aFileIter")
+ } else {
+ isOk and copyAssetFolder(theAssetMgr,
+ "$theAssetFolder/$aFileIter",
+ "$theFolderPathTo/$aFileIter")
+ }
+ }
+ isOk
+ } catch (theError: Exception) {
+ theError.printStackTrace()
+ false
+ }
+ }
+
+ //! Copy single file from assets
+ private fun copyAsset(theAssetMgr: AssetManager,
+ thePathFrom: String,
+ thePathTo: String): Boolean {
+ return try {
+ val aStreamIn = theAssetMgr.open(thePathFrom)
+ val aFileTo = File(thePathTo)
+ aFileTo.createNewFile()
+ val aStreamOut: OutputStream? = FileOutputStream(thePathTo)
+ copyStreamContent(aStreamIn, aStreamOut)
+ aStreamIn.close()
+ aStreamOut!!.flush()
+ aStreamOut.close()
+ true
+ } catch (theError: Exception) {
+ theError.printStackTrace()
+ false
+ }
+ }
+
+ //! Show/hide text view
+ private fun switchTextView(theTextView: TextView?,
+ theClickedBtn: ImageButton,
+ theToSwitchOn: Boolean) {
+ if (theTextView != null && theTextView.visibility == View.GONE && theToSwitchOn) {
+ theTextView.visibility = View.VISIBLE
+ theClickedBtn.setBackgroundColor(resources.getColor(R.color.pressedBtnColor))
+ setTextViewPosition(theTextView)
+ } else {
+ theTextView!!.visibility = View.GONE
+ theClickedBtn.setBackgroundColor(resources.getColor(R.color.btnColor))
+ }
+ }
+
+ //! Setup text view position
+ private fun setTextViewPosition(theTextView: TextView?) {
+ if (theTextView!!.visibility != View.VISIBLE) {
+ return
+ }
+ if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ theTextView.x = myButtonPreferSize.toFloat()
+ theTextView.y = 0f
+ } else {
+ theTextView.x = 0f
+ theTextView.y = myButtonPreferSize.toFloat()
+ }
+ }
+
+ override fun onClick(theButton: View) {
+ val aClickedBtn = theButton as ImageButton
+ when (aClickedBtn.id) {
+ R.id.message -> {
+ switchTextView(findViewById(R.id.info_view) as TextView,
+ findViewById(R.id.info) as ImageButton, false)
+ switchTextView(myMessageTextView, aClickedBtn, true)
+ return
+ }
+ R.id.info -> {
+ var aText = getString(R.string.info_html)
+ aText = String.format(aText, cppOcctMajorVersion(), cppOcctMinorVersion(), cppOcctMicroVersion())
+ val aSpanned = Html.fromHtml(aText, ImageGetter { theSource ->
+ val aResources = resources
+ val anId = aResources.getIdentifier(theSource, "drawable", packageName)
+ val aRes = aResources.getDrawable(anId)
+ aRes.setBounds(0, 0, aRes.intrinsicWidth, aRes.intrinsicHeight)
+ aRes
+ }, null)
+ val anInfoView = findViewById(R.id.info_view) as TextView
+ anInfoView.text = aSpanned
+ switchTextView(myMessageTextView, findViewById(R.id.message) as ImageButton, false)
+ switchTextView(anInfoView, aClickedBtn, true)
+ return
+ }
+ R.id.fit -> {
+ myOcctView!!.fitAll()
+ return
+ }
+ R.id.proj_front -> {
+ myOcctView!!.setProj(OcctJniRenderer.TypeOfOrientation.Xpos)
+ return
+ }
+ R.id.proj_left -> {
+ myOcctView!!.setProj(OcctJniRenderer.TypeOfOrientation.Yneg)
+ return
+ }
+ R.id.proj_top -> {
+ myOcctView!!.setProj(OcctJniRenderer.TypeOfOrientation.Zpos)
+ return
+ }
+ R.id.proj_back -> {
+ myOcctView!!.setProj(OcctJniRenderer.TypeOfOrientation.Xneg)
+ return
+ }
+ R.id.proj_right -> {
+ myOcctView!!.setProj(OcctJniRenderer.TypeOfOrientation.Ypos)
+ return
+ }
+ R.id.proj_bottom -> {
+ myOcctView!!.setProj(OcctJniRenderer.TypeOfOrientation.Zneg)
+ return
+ }
+ R.id.open -> {
+ val aPath = Environment.getExternalStorageDirectory()
+ aClickedBtn.setBackgroundColor(resources.getColor(R.color.pressedBtnColor))
+ if (myFileOpenDialog == null) {
+ // should be requested on runtime since API level 26 (Android 8)
+ askUserPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, null) // for accessing SD card
+ myFileOpenDialog = OcctJniFileDialog(this, aPath)
+ myFileOpenDialog!!.setFileEndsWith(".brep")
+ myFileOpenDialog!!.setFileEndsWith(".rle")
+ myFileOpenDialog!!.setFileEndsWith(".iges")
+ myFileOpenDialog!!.setFileEndsWith(".igs")
+ myFileOpenDialog!!.setFileEndsWith(".step")
+ myFileOpenDialog!!.setFileEndsWith(".stp")
+ myFileOpenDialog!!.setFileEndsWith(".stl")
+
+ myFileOpenDialog!!.addFileListener (object : OcctJniFileDialog.FileSelectedListener {
+ override fun fileSelected(theFile: File?) {
+ if (theFile != null && myOcctView != null) {
+ myOcctView!!.open(theFile.getPath())
+ }
+ }
+ })
+
+ myFileOpenDialog!!.addDialogDismissedListener (object : OcctJniFileDialog.DialogDismissedListener {
+ override fun dialogDismissed() {
+ val openButton = findViewById(R.id.open) as ImageButton
+ openButton.setBackgroundColor(resources.getColor(R.color.btnColor))
+ }
+ })
+ }
+ myFileOpenDialog!!.showDialog()
+ return
+ }
+ }
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun createSubmenuBtn(theParentBtnId: Int,
+ theParentLayoutId: Int,
+ theNewButtonIds: List,
+ theNewButtonImageIds: List,
+ thePosition: Int) {
+ var aPosInList = 0
+ val aParentBtn = findViewById(theParentBtnId) as? ImageButton
+ val aParams: ViewGroup.LayoutParams? = null
+ val parentLayout = findViewById(theParentLayoutId) as LinearLayout
+ for (newButtonId in theNewButtonIds) {
+ var aNewButton = findViewById(newButtonId) as? ImageButton
+ if (aNewButton == null) {
+ aNewButton = ImageButton(this)
+ aNewButton.id = newButtonId
+ aNewButton.setImageResource(theNewButtonImageIds[aPosInList])
+ aNewButton.layoutParams = aParams
+ parentLayout.addView(aNewButton)
+ }
+ aNewButton.setOnClickListener(this)
+ aNewButton.visibility = View.GONE
+ aNewButton.setOnTouchListener { theView, theEvent -> onTouchButton(theView, theEvent) }
+ ++aPosInList
+ }
+ if (aParentBtn != null) {
+ aParentBtn.setOnTouchListener(null)
+ aParentBtn.setOnTouchListener { theView, theEvent ->
+ if (theView == null) {} // dummy
+ if (theEvent.action == MotionEvent.ACTION_DOWN) {
+ var isVisible = false
+ for (aNewButtonId in theNewButtonIds) {
+ val anBtn = findViewById(aNewButtonId) as? ImageButton
+ if (anBtn != null) {
+ if (anBtn.visibility == View.GONE) {
+ anBtn.visibility = View.VISIBLE
+ isVisible = true
+ } else {
+ anBtn.visibility = View.GONE
+ }
+ }
+ }
+ aParentBtn.setBackgroundColor(if (!isVisible) resources.getColor(R.color.btnColor) else resources.getColor(R.color.pressedBtnColor))
+ }
+ false
+ }
+ }
+ }
+
+ //! Implements onTouch functionality
+ private fun onTouchButton(theView: View,
+ theEvent: MotionEvent): Boolean {
+ when (theEvent.action) {
+ MotionEvent.ACTION_DOWN -> (theView as ImageButton).setBackgroundColor(resources.getColor(R.color.pressedBtnColor))
+ MotionEvent.ACTION_UP -> (theView as ImageButton).setBackgroundColor(resources.getColor(R.color.btnColor))
+ }
+ return false
+ }
+
+ //! Handle configuration change event
+ override fun onConfigurationChanged(theNewConfig: Configuration) {
+ super.onConfigurationChanged(theNewConfig)
+ val aLayoutPanelMenu = findViewById(R.id.panel_menu) as LinearLayout
+ val aPanelMenuLayoutParams = aLayoutPanelMenu.layoutParams
+ val aLayoutViewGroup = findViewById(R.id.view_group) as LinearLayout
+ val aViewGroupLayoutParams = aLayoutViewGroup.layoutParams
+ val aScrollBtn = findViewById(R.id.scroll_btn) as ImageButton
+ val aScrollBtnLayoutParams = aScrollBtn.layoutParams
+ myButtonPreferSize = defineButtonSize(findViewById(R.id.panel_menu) as LinearLayout)
+ defineButtonSize(findViewById(R.id.view_group) as LinearLayout)
+ when (theNewConfig.orientation) {
+ Configuration.ORIENTATION_PORTRAIT -> {
+ setHorizontal(aLayoutPanelMenu, aPanelMenuLayoutParams)
+ setHorizontal(aLayoutViewGroup, aViewGroupLayoutParams)
+ aLayoutViewGroup.setGravity(Gravity.BOTTOM)
+ aScrollBtnLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ aScrollBtnLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
+ aScrollBtn.layoutParams = aScrollBtnLayoutParams
+ if (aLayoutPanelMenu.visibility == View.VISIBLE) {
+ aScrollBtn.setImageResource(R.drawable.close_p)
+ aScrollBtn.y = myButtonPreferSize.toFloat()
+ aScrollBtn.x = 0f
+ } else {
+ aScrollBtn.setImageResource(R.drawable.open_p)
+ aScrollBtn.y = 0f
+ aScrollBtn.x = 0f
+ }
+ }
+ Configuration.ORIENTATION_LANDSCAPE -> {
+ setVertical(aLayoutPanelMenu, aPanelMenuLayoutParams)
+ setVertical(aLayoutViewGroup, aViewGroupLayoutParams)
+ aLayoutViewGroup.setGravity(Gravity.RIGHT)
+ aScrollBtnLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
+ aScrollBtnLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
+ aScrollBtn.layoutParams = aScrollBtnLayoutParams
+ if (aLayoutPanelMenu.visibility == View.VISIBLE) {
+ aScrollBtn.setImageResource(R.drawable.close_l)
+ aScrollBtn.x = myButtonPreferSize.toFloat()
+ aScrollBtn.y = 0f
+ } else {
+ aScrollBtn.setImageResource(R.drawable.open_l)
+ aScrollBtn.y = 0f
+ aScrollBtn.x = 0f
+ }
+ }
+ }
+ setTextViewPosition(myMessageTextView)
+ setTextViewPosition(findViewById(R.id.info_view) as TextView)
+ }
+
+ private fun setHorizontal(theLayout: LinearLayout,
+ theLayoutParams: ViewGroup.LayoutParams) {
+ theLayout.orientation = LinearLayout.HORIZONTAL
+ theLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ theLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
+ theLayout.layoutParams = theLayoutParams
+ }
+
+ private fun setVertical(theLayout: LinearLayout,
+ theLayoutParams: ViewGroup.LayoutParams) {
+ theLayout.orientation = LinearLayout.VERTICAL
+ theLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
+ theLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
+ theLayout.layoutParams = theLayoutParams
+ }
+
+ //! Define button size
+ private fun defineButtonSize(theLayout: LinearLayout): Int {
+ val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val aDisplay = windowManager.defaultDisplay
+ val aDispPnt = Point()
+ aDisplay.getSize(aDispPnt)
+ val aNbChildren = theLayout.childCount
+ val aHeight = aDispPnt.y / aNbChildren
+ val aWidth = aDispPnt.x / aNbChildren
+
+ for (aChildIter in 0 until aNbChildren) {
+ val aView = theLayout.getChildAt(aChildIter)
+ if (aView is ImageButton) {
+ val aButton = aView
+ if (isLandscape) {
+ aButton.minimumWidth = aHeight
+ } else {
+ aButton.minimumHeight = aWidth
+ }
+ }
+ }
+ return if (isLandscape) { aHeight } else { aWidth }
+ }
+
+ //! Request user permission.
+ private fun askUserPermission(thePermission: String, theRationale: String?) {
+ // Dynamically load methods introduced by API level 23.
+ // On older system this permission is granted by user during application installation.
+ val aMetPtrCheckSelfPermission: Method
+ val aMetPtrRequestPermissions: Method
+ val aMetPtrShouldShowRequestPermissionRationale: Method
+ try {
+ aMetPtrCheckSelfPermission = myContext!!.javaClass.getMethod("checkSelfPermission", String::class.java)
+ aMetPtrRequestPermissions = javaClass.getMethod("requestPermissions", Array::class.java, Int::class.javaPrimitiveType)
+ aMetPtrShouldShowRequestPermissionRationale = javaClass.getMethod("shouldShowRequestPermissionRationale", String::class.java)
+ } catch (theError: SecurityException) {
+ postMessage("""
+ Unable to find permission methods:
+ ${theError.message}
+ """.trimIndent(), Message_Trace)
+ return
+ } catch (theError: NoSuchMethodException) {
+ postMessage("""
+ Unable to find permission methods:
+ ${theError.message}
+ """.trimIndent(), Message_Trace)
+ return
+ }
+ try {
+ val isAlreadyGranted = aMetPtrCheckSelfPermission.invoke(myContext, thePermission) as Int
+ if (isAlreadyGranted == PackageManager.PERMISSION_GRANTED) {
+ return
+ }
+ val toShowInfo = theRationale != null && aMetPtrShouldShowRequestPermissionRationale.invoke(this, thePermission) as Boolean
+ if (toShowInfo) {
+ postMessage(theRationale, Message_Info)
+ }
+
+ // show dialog to user
+ aMetPtrRequestPermissions.invoke(this, arrayOf(thePermission), 0)
+ } catch (theError: IllegalArgumentException) {
+ postMessage("""
+ Internal error: Unable to call permission method:
+ ${theError.message}
+ """.trimIndent(), Message_Fail)
+ return
+ } catch (theError: IllegalAccessException) {
+ postMessage("""
+ Internal error: Unable to call permission method:
+ ${theError.message}
+ """.trimIndent(), Message_Fail)
+ return
+ } catch (theError: InvocationTargetException) {
+ postMessage("""
+ Internal error: Unable to call permission method:
+ ${theError.message}
+ """.trimIndent(), Message_Fail)
+ return
+ }
+ }
+
+ //! Auxiliary method to show info message.
+ fun postMessage(theMessage: String?, theGravity: Int) {
+ if (theGravity == Message_Trace) {
+ return
+ }
+ val aCtx: Context = this
+ runOnUiThread {
+ val aBuilder = AlertDialog.Builder(aCtx)
+ aBuilder.setMessage(theMessage).setNegativeButton("OK", null)
+ val aDialog = aBuilder.create()
+ aDialog.show()
+ }
+ }
+
+ //! OCCT major version
+ private external fun cppOcctMajorVersion(): Long
+
+ //! OCCT minor version
+ private external fun cppOcctMinorVersion(): Long
+
+ //! OCCT micro version
+ private external fun cppOcctMicroVersion(): Long
+ private var myOcctView: OcctJniView? = null
+ private var myMessageTextView: TextView? = null
+ private var myLastPath: String? = null
+ private var myContext: ContextWrapper? = null
+ private var myFileOpenDialog: OcctJniFileDialog? = null
+ private var myButtonPreferSize = 65
+
+ companion object {
+ //! Auxiliary method to print temporary info messages
+ fun printShortInfo(theActivity: Activity,
+ theInfo: CharSequence?) {
+ val aCtx = theActivity.applicationContext
+ val aToast = Toast.makeText(aCtx, theInfo, Toast.LENGTH_LONG)
+ aToast.show()
+ }
+
+ //! Load single native library
+ private fun loadLibVerbose(theLibName: String,
+ theLoadedInfo: StringBuilder,
+ theFailedInfo: StringBuilder): Boolean {
+ return try {
+ System.loadLibrary(theLibName)
+ theLoadedInfo.append("Info: native library \"")
+ theLoadedInfo.append(theLibName)
+ theLoadedInfo.append("\" has been loaded\n")
+ true
+ } catch (theError: UnsatisfiedLinkError) {
+ theFailedInfo.append("Error: native library \"")
+ theFailedInfo.append(theLibName)
+ theFailedInfo.append("""" is unavailable:
+ ${theError.message}""")
+ false
+ } catch (theError: SecurityException) {
+ theFailedInfo.append("Error: native library \"")
+ theFailedInfo.append(theLibName)
+ theFailedInfo.append("""" can not be loaded for security reasons:
+ ${theError.message}""")
+ false
+ }
+ }
+
+ var wasNativesLoadCalled = false
+ @JvmField
+ var areNativeLoaded = false
+ var nativeLoaded = ""
+ var nativeFailed = ""
+
+ //! Copy single file
+ @Throws(IOException::class)
+ private fun copyStreamContent(theIn: InputStream?,
+ theOut: OutputStream?) {
+ val aBuffer = ByteArray(1024)
+ var aNbReadBytes: Int
+ while (theIn!!.read(aBuffer).also { aNbReadBytes = it } != -1) {
+ theOut!!.write(aBuffer, 0, aNbReadBytes)
+ }
+ }
+
+ //! Message gravity.
+ private const val Message_Trace = 0
+ private const val Message_Info = 1
+ private const val Message_Warning = 2
+ private const val Message_Alarm = 3
+ private const val Message_Fail = 4
+ }
+}
diff --git a/app/src/main/java/com/opencascade/jnisample/OcctJniFileDialog.kt b/app/src/main/java/com/opencascade/jnisample/OcctJniFileDialog.kt
new file mode 100644
index 0000000..bbbc119
--- /dev/null
+++ b/app/src/main/java/com/opencascade/jnisample/OcctJniFileDialog.kt
@@ -0,0 +1,213 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+package com.opencascade.jnisample
+
+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.ViewGroup
+import android.widget.*
+import android.widget.AdapterView.OnItemClickListener
+import com.opencascade.jnisample.ListenerList.FireHandler
+import java.io.File
+import java.io.FilenameFilter
+import java.util.*
+
+//! Simple open file dialog
+class OcctJniFileDialog(theActivity: Activity,
+ thePath: File) {
+
+ private var myFileList: Array? = emptyArray()
+ private var myCurrentPath: File? = null
+ private val myFileListenerList = ListenerList()
+ private val myDialogDismissedList = ListenerList()
+ private val myActivity: Activity
+ private var myFileEndsWith: MutableList? = null
+
+ //! Main constructor.
+ init {
+ var aPath = thePath
+ myActivity = theActivity
+ if (!aPath.exists()) {
+ aPath = Environment.getExternalStorageDirectory()
+ }
+ loadFileList(aPath)
+ }
+
+ interface FileSelectedListener {
+ fun fileSelected(theFile: File?)
+ }
+
+ interface DialogDismissedListener {
+ fun dialogDismissed()
+ }
+
+ //! Create new dialog
+ fun createFileDialog(): Dialog? {
+ val anObjWrapper = arrayOfNulls(1)
+ val aBuilder = AlertDialog.Builder(myActivity)
+ aBuilder.setTitle(myCurrentPath!!.path)
+ val aTitleLayout = LinearLayout(myActivity)
+ aTitleLayout.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ aTitleLayout.orientation = LinearLayout.VERTICAL
+ val list = ListView(myActivity)
+ list.isScrollingCacheEnabled = false
+ list.setBackgroundColor(Color.parseColor("#33B5E5"))
+ list.adapter = ArrayAdapter(myActivity, android.R.layout.select_dialog_item, myFileList)
+ list.onItemClickListener = OnItemClickListener { arg0, view, pos, id ->
+ if (arg0 == null || view == null || id == 0L) {} // dummy
+ val fileChosen = myFileList!![pos]
+ val aChosenFile = getChosenFile(fileChosen)
+ if (aChosenFile.isDirectory) {
+ loadFileList(aChosenFile)
+ (anObjWrapper[0] as Dialog?)!!.cancel()
+ (anObjWrapper[0] as Dialog?)!!.dismiss()
+ showDialog()
+ } else {
+ (anObjWrapper[0] as Dialog?)!!.cancel()
+ (anObjWrapper[0] as Dialog?)!!.dismiss()
+ fireFileSelectedEvent(aChosenFile)
+ }
+ }
+ list.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0.6f)
+ aTitleLayout.addView(list)
+ aBuilder.setNegativeButton("Cancel", null)
+ aBuilder.setView(aTitleLayout)
+
+ val aDialog = aBuilder.show()
+ aDialog.setOnDismissListener(DialogInterface.OnDismissListener { fireDialogDismissedEvent() })
+ anObjWrapper[0] = aDialog
+ return aDialog
+ }
+
+ fun addFileListener(theListener: FileSelectedListener) {
+ myFileListenerList.add(theListener)
+ }
+
+ fun addDialogDismissedListener(theListener: DialogDismissedListener) {
+ myDialogDismissedList.add(theListener)
+ }
+
+ //! Show file dialog
+ fun showDialog() {
+ createFileDialog()!!.show()
+ }
+
+ private fun fireFileSelectedEvent(theFile: File) {
+ myFileListenerList.fireEvent(object : FireHandler {
+ override fun fireEvent(theListener: FileSelectedListener) {
+ theListener.fileSelected(theFile)
+ }
+ })
+ }
+
+ private fun fireDialogDismissedEvent() {
+ myDialogDismissedList.fireEvent(object : FireHandler {
+ override fun fireEvent(theListener: DialogDismissedListener) {
+ theListener.dialogDismissed()
+ }
+ })
+ }
+
+ private fun loadFileList(thePath: File?) {
+ myCurrentPath = thePath
+ val aList: MutableList = ArrayList()
+ if (thePath!!.exists()) {
+ if (thePath.parentFile != null) {
+ aList.add(PARENT_DIR)
+ }
+ val aFilter = FilenameFilter { theDir, theFilename ->
+ val aSel = File(theDir, theFilename)
+ if (!aSel.canRead()) {
+ return@FilenameFilter false
+ }
+ var isEndWith = false
+ if (myFileEndsWith != null) {
+ for (aFileExtIter in myFileEndsWith!!) {
+ if (theFilename.toLowerCase().endsWith(aFileExtIter)) {
+ isEndWith = true
+ break
+ }
+ }
+ }
+ isEndWith || aSel.isDirectory
+ }
+ val aFileList1 = thePath.list(aFilter)
+ if (aFileList1 != null) {
+ for (aFileIter in aFileList1) {
+ aList.add(aFileIter)
+ }
+ }
+ }
+ myFileList = aList.toTypedArray()
+ }
+
+ private fun getChosenFile(theFileChosen: String): File {
+ return if (theFileChosen == PARENT_DIR) myCurrentPath!!.parentFile else File(myCurrentPath, theFileChosen)
+ }
+
+ fun setFileEndsWith(fileEndsWith: String) {
+ if (myFileEndsWith == null) {
+ myFileEndsWith = ArrayList()
+ }
+ if (myFileEndsWith!!.indexOf(fileEndsWith) == -1) {
+ myFileEndsWith!!.add(fileEndsWith)
+ }
+ }
+
+ fun setFileEndsWith(theFileEndsWith: MutableList?) {
+ myFileEndsWith = theFileEndsWith
+ }
+
+ companion object {
+ private const val PARENT_DIR = ".."
+ }
+}
+
+internal class ListenerList {
+ private val myListenerList: MutableList = ArrayList()
+
+ interface FireHandler {
+ fun fireEvent(theListener: L)
+ }
+
+ fun add(theListener: L) {
+ myListenerList.add(theListener)
+ }
+
+ fun fireEvent(theFireHandler: FireHandler) {
+ val aCopy: List = ArrayList(myListenerList)
+ for (anIter in aCopy) {
+ theFireHandler.fireEvent(anIter)
+ }
+ }
+
+ fun remove(theListener: L) {
+ myListenerList.remove(theListener)
+ }
+
+ val listenerList: List
+ get() = myListenerList
+}
diff --git a/app/src/main/java/com/opencascade/jnisample/OcctJniLogger.kt b/app/src/main/java/com/opencascade/jnisample/OcctJniLogger.kt
new file mode 100644
index 0000000..f1bd685
--- /dev/null
+++ b/app/src/main/java/com/opencascade/jnisample/OcctJniLogger.kt
@@ -0,0 +1,68 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+package com.opencascade.jnisample
+
+import android.util.Log
+import android.widget.TextView
+import java.util.concurrent.locks.ReentrantLock
+
+//! Auxiliary class for logging messages
+object OcctJniLogger {
+ //! Setup text view
+ fun setTextView(theTextView: TextView?) {
+ if (myTextView != null) {
+ myLog = myTextView!!.text.toString()
+ }
+ myTextView = theTextView
+ if (myTextView != null) {
+ myTextView!!.text = myLog
+ myLog = ""
+ }
+ }
+
+ //! Interface implementation
+ @JvmStatic
+ fun postMessage(theText: String?) {
+ var aCopy = String()
+ aCopy += theText
+ Log.e(myTag, theText)
+ myMutex.lock()
+ val aView = myTextView
+ if (aView == null) {
+ myLog += aCopy
+ myMutex.unlock()
+ return
+ }
+ aView.post(Runnable {
+ aView.text = """
+ ${aView.text}$aCopy
+
+ """.trimIndent()
+ })
+ myMutex.unlock()
+ }
+
+ private const val myTag = "occtJniViewer"
+ private val myMutex = ReentrantLock(true)
+ private var myTextView: TextView? = null
+ private var myLog = ""
+}
diff --git a/app/src/main/java/com/opencascade/jnisample/OcctJniRenderer.kt b/app/src/main/java/com/opencascade/jnisample/OcctJniRenderer.kt
new file mode 100644
index 0000000..3eeb7fa
--- /dev/null
+++ b/app/src/main/java/com/opencascade/jnisample/OcctJniRenderer.kt
@@ -0,0 +1,186 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+package com.opencascade.jnisample
+
+import android.opengl.GLSurfaceView
+import javax.microedition.khronos.egl.EGLConfig
+import javax.microedition.khronos.opengles.GL10
+
+//! Wrapper for C++ OCCT viewer.
+class OcctJniRenderer internal constructor(theView: GLSurfaceView?,
+ theScreenDensity: Float) : GLSurfaceView.Renderer {
+ //! Wrapper for V3d_TypeOfOrientation
+ enum class TypeOfOrientation {
+ Xpos, // front
+ Ypos, // left
+ Zpos, // top
+ Xneg, // back
+ Yneg, // right
+ Zneg // bottom
+ }
+
+ //! Open file.
+ fun open(thePath: String) {
+ if (myCppViewer != 0L) {
+ cppOpen(myCppViewer, thePath)
+ }
+ }
+
+ //! Update viewer.
+ override fun onDrawFrame(theGl: GL10) {
+ if (myCppViewer != 0L) {
+ if (cppRedraw(myCppViewer)) {
+ myView!!.requestRender() // this method is allowed from any thread
+ }
+ }
+ }
+
+ //! (re)initialize viewer.
+ override fun onSurfaceChanged(theGl: GL10, theWidth: Int, theHeight: Int) {
+ if (myCppViewer != 0L) {
+ cppResize(myCppViewer, theWidth, theHeight)
+ }
+ }
+
+ override fun onSurfaceCreated(theGl: GL10, theEglConfig: EGLConfig) {
+ if (myCppViewer != 0L) {
+ cppInit(myCppViewer)
+ }
+ }
+
+ //! Add touch point.
+ fun onAddTouchPoint(theId: Int, theX: Float, theY: Float) {
+ if (myCppViewer != 0L) {
+ cppAddTouchPoint(myCppViewer, theId, theX, theY)
+ }
+ }
+
+ //! Update touch point.
+ fun onUpdateTouchPoint(theId: Int, theX: Float, theY: Float) {
+ if (myCppViewer != 0L) {
+ cppUpdateTouchPoint(myCppViewer, theId, theX, theY)
+ }
+ }
+
+ //! Remove touch point.
+ fun onRemoveTouchPoint(theId: Int) {
+ if (myCppViewer != 0L) {
+ cppRemoveTouchPoint(myCppViewer, theId)
+ }
+ }
+
+ //! Select in 3D Viewer.
+ fun onSelectInViewer(theX: Float, theY: Float) {
+ if (myCppViewer != 0L) {
+ cppSelectInViewer(myCppViewer, theX, theY)
+ }
+ }
+
+ //! Fit All
+ fun fitAll() {
+ if (myCppViewer != 0L) {
+ cppFitAll(myCppViewer)
+ }
+ }
+
+ //! Move camera
+ fun setProj(theProj: TypeOfOrientation?) {
+ if (myCppViewer == 0L) {
+ return
+ }
+ when (theProj) {
+ TypeOfOrientation.Xpos -> cppSetXposProj(myCppViewer)
+ TypeOfOrientation.Ypos -> cppSetYposProj(myCppViewer)
+ TypeOfOrientation.Zpos -> cppSetZposProj(myCppViewer)
+ TypeOfOrientation.Xneg -> cppSetXnegProj(myCppViewer)
+ TypeOfOrientation.Yneg -> cppSetYnegProj(myCppViewer)
+ TypeOfOrientation.Zneg -> cppSetZnegProj(myCppViewer)
+ }
+ }
+
+ //! Post message to the text view.
+ fun postMessage(theText: String?) {
+ OcctJniLogger.postMessage(theText)
+ }
+
+ //! Create instance of C++ class
+ private external fun cppCreate(theDispDensity: Float): Long
+
+ //! Destroy instance of C++ class
+ private external fun cppDestroy(theCppPtr: Long)
+
+ //! Initialize OCCT viewer (steal OpenGL ES context bound to this thread)
+ private external fun cppInit(theCppPtr: Long)
+
+ //! Resize OCCT viewer
+ private external fun cppResize(theCppPtr: Long, theWidth: Int, theHeight: Int)
+
+ //! Open CAD file
+ private external fun cppOpen(theCppPtr: Long, thePath: String)
+
+ //! Add touch point
+ private external fun cppAddTouchPoint(theCppPtr: Long, theId: Int, theX: Float, theY: Float)
+
+ //! Update touch point
+ private external fun cppUpdateTouchPoint(theCppPtr: Long, theId: Int, theX: Float, theY: Float)
+
+ //! Remove touch point
+ private external fun cppRemoveTouchPoint(theCppPtr: Long, theId: Int)
+
+ //! Select in 3D Viewer.
+ private external fun cppSelectInViewer(theCppPtr: Long, theX: Float, theY: Float)
+
+ //! Redraw OCCT viewer
+ //! Returns TRUE if more frames are requested.
+ private external fun cppRedraw(theCppPtr: Long): Boolean
+
+ //! Fit All
+ private external fun cppFitAll(theCppPtr: Long)
+
+ //! Move camera
+ private external fun cppSetXposProj(theCppPtr: Long)
+
+ //! Move camera
+ private external fun cppSetYposProj(theCppPtr: Long)
+
+ //! Move camera
+ private external fun cppSetZposProj(theCppPtr: Long)
+
+ //! Move camera
+ private external fun cppSetXnegProj(theCppPtr: Long)
+
+ //! Move camera
+ private external fun cppSetYnegProj(theCppPtr: Long)
+
+ //! Move camera
+ private external fun cppSetZnegProj(theCppPtr: Long)
+ private var myView: GLSurfaceView? = null //!< back reference to the View
+ private var myCppViewer: Long = 0 //!< pointer to c++ class instance
+
+ //! Empty constructor.
+ init {
+ myView = theView // this makes cyclic dependency, but it is OK for JVM
+ if (OcctJniActivity.areNativeLoaded) {
+ myCppViewer = cppCreate(theScreenDensity)
+ }
+ }
+}
diff --git a/app/src/main/java/com/opencascade/jnisample/OcctJniView.kt b/app/src/main/java/com/opencascade/jnisample/OcctJniView.kt
new file mode 100644
index 0000000..971aa19
--- /dev/null
+++ b/app/src/main/java/com/opencascade/jnisample/OcctJniView.kt
@@ -0,0 +1,250 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+package com.opencascade.jnisample
+
+import android.app.ActionBar
+import android.content.Context
+import android.graphics.PointF
+import android.opengl.GLSurfaceView
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.RelativeLayout
+import com.opencascade.jnisample.OcctJniLogger.postMessage
+import com.opencascade.jnisample.OcctJniRenderer.TypeOfOrientation
+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.
+internal class OcctJniView(theContext: Context,
+ theAttrs: AttributeSet?) : GLSurfaceView(theContext, theAttrs) {
+ //! Open file.
+ fun open(thePath: String) {
+ queueEvent { myRenderer!!.open(thePath) }
+ requestRender()
+ }
+
+ //! Create OpenGL ES 2.0+ context
+ private class ContextFactory : EGLContextFactory {
+ override fun createContext(theEgl: EGL10,
+ theEglDisplay: EGLDisplay,
+ theEglConfig: EGLConfig): EGLContext {
+ // reset EGL errors stack
+ while (theEgl.eglGetError() != EGL10.EGL_SUCCESS) {}
+ val anAttribs = intArrayOf(EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE)
+ val aEglContext = theEgl.eglCreateContext(theEglDisplay, theEglConfig, EGL10.EGL_NO_CONTEXT, anAttribs)
+ var anError = theEgl.eglGetError()
+ while (anError != EGL10.EGL_SUCCESS) {
+ postMessage("Error: eglCreateContext() " + String.format("0x%x", anError))
+ anError = theEgl.eglGetError()
+ }
+ return aEglContext
+ }
+
+ override fun destroyContext(theEgl: EGL10,
+ theEglDisplay: EGLDisplay,
+ theEglContext: EGLContext) {
+ theEgl.eglDestroyContext(theEglDisplay, theEglContext)
+ }
+
+ companion object {
+ private const val EGL_CONTEXT_CLIENT_VERSION = 0x3098
+ }
+ }
+
+ //! Search for RGB24 config with depth and stencil buffers
+ private class ConfigChooser : EGLConfigChooser {
+ //! Reset EGL errors stack
+ private fun popEglErrors(theEgl: EGL10) {
+ var anError = theEgl.eglGetError()
+ while (anError != EGL10.EGL_SUCCESS) {
+ postMessage("EGL Error: " + String.format("0x%x", anError))
+ anError = theEgl.eglGetError()
+ }
+ }
+
+ //! Auxiliary method to dump EGL configuration - for debugging purposes
+ private fun printConfig(theEgl: EGL10,
+ theEglDisplay: EGLDisplay,
+ theEglConfig: EGLConfig) {
+ val THE_ATTRIBS = intArrayOf(
+ 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
+ )
+ val THE_NAMES = arrayOf(
+ "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"
+ )
+ val aValue = IntArray(1)
+ for (anAttrIter in THE_ATTRIBS.indices) {
+ val anAttr = THE_ATTRIBS[anAttrIter]
+ val aName = THE_NAMES[anAttrIter]
+ if (theEgl.eglGetConfigAttrib(theEglDisplay, theEglConfig, anAttr, aValue)) {
+ postMessage(String.format(" %s: %d\n", aName, aValue[0]))
+ } else {
+ popEglErrors(theEgl)
+ }
+ }
+ }
+
+ //! Interface implementation
+ override fun chooseConfig(theEgl: EGL10,
+ theEglDisplay: EGLDisplay): EGLConfig {
+ val EGL_OPENGL_ES2_BIT = 4
+ val aCfgAttribs = intArrayOf(
+ 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
+ )
+ val aConfigs = arrayOfNulls(1)
+ val aNbConfigs = IntArray(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) {
+ postMessage("Error: eglChooseConfig() has failed!")
+ ///return null
+ }
+ }
+
+ //printConfig (theEgl, theEglDisplay, aConfigs[0]);
+ return aConfigs[0]!!
+ }
+ }
+
+ //! Callback to handle touch events
+ override fun onTouchEvent(theEvent: MotionEvent): Boolean {
+ val aMaskedAction = theEvent.actionMasked
+ when (aMaskedAction) {
+ MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
+ val aPointerIndex = theEvent.actionIndex
+ val aPointerId = theEvent.getPointerId(aPointerIndex)
+ val aPnt = PointF(theEvent.getX(aPointerIndex), theEvent.getY(aPointerIndex))
+ mySelectPoint = if (theEvent.pointerCount == 1) {
+ aPnt
+ } else {
+ null
+ }
+ queueEvent { myRenderer!!.onAddTouchPoint(aPointerId, aPnt.x, aPnt.y) }
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val aNbPointers = theEvent.pointerCount
+ var aPntIter = 0
+ while (aPntIter < aNbPointers) {
+ val aPointerId = theEvent.getPointerId(aPntIter)
+ val aPnt = PointF(theEvent.getX(aPntIter), theEvent.getY(aPntIter))
+ queueEvent { myRenderer!!.onUpdateTouchPoint(aPointerId, aPnt.x, aPnt.y) }
+ ++aPntIter
+ }
+ if (mySelectPoint != null) {
+ val aTouchThreshold = 5.0f * myScreenDensity
+ val aPointerIndex = theEvent.actionIndex
+ val aDelta = PointF(theEvent.getX(aPointerIndex) - mySelectPoint!!.x, theEvent.getY(aPointerIndex) - mySelectPoint!!.y)
+ if (Math.abs(aDelta.x) > aTouchThreshold || Math.abs(aDelta.y) > aTouchThreshold) {
+ mySelectPoint = null
+ }
+ }
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> {
+ if (mySelectPoint != null) {
+ val aSelX = mySelectPoint!!.x
+ val aSelY = mySelectPoint!!.y
+ queueEvent { myRenderer!!.onSelectInViewer(aSelX, aSelY) }
+ mySelectPoint = null
+ }
+ val aPointerIndex = theEvent.actionIndex
+ val aPointerId = theEvent.getPointerId(aPointerIndex)
+ //val aPnt = PointF(theEvent.getX(aPointerIndex), theEvent.getY(aPointerIndex))
+ queueEvent { myRenderer!!.onRemoveTouchPoint(aPointerId) }
+ }
+ }
+ requestRender()
+ return true
+ }
+
+ //! Fit All
+ fun fitAll() {
+ queueEvent { myRenderer!!.fitAll() }
+ requestRender()
+ }
+
+ //! Move camera
+ fun setProj(theProj: TypeOfOrientation?) {
+ queueEvent { myRenderer!!.setProj(theProj) }
+ requestRender()
+ }
+
+ //! OCCT viewer
+ private var myRenderer: OcctJniRenderer? = null
+ private val mySelectId = -1
+ private var mySelectPoint: PointF? = null
+ private var myScreenDensity = 1.0f
+
+ // ! Default constructor.
+ init {
+ val aDispInfo = theContext.resources.displayMetrics
+ myScreenDensity = aDispInfo.density
+ preserveEGLContextOnPause = true
+ setEGLContextFactory(ContextFactory())
+ setEGLConfigChooser(ConfigChooser())
+ val aLParams = RelativeLayout.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.WRAP_CONTENT)
+ aLParams.addRule(RelativeLayout.ALIGN_TOP)
+ myRenderer = OcctJniRenderer(this, myScreenDensity)
+ setRenderer(myRenderer)
+ renderMode = RENDERMODE_WHEN_DIRTY // render on request to spare battery
+ }
+}
diff --git a/app/src/main/jni/CMakeLists.txt b/app/src/main/jni/CMakeLists.txt
new file mode 100644
index 0000000..59e41e6
--- /dev/null
+++ b/app/src/main/jni/CMakeLists.txt
@@ -0,0 +1,44 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+set(HEADER_FILES OcctJni_MsgPrinter.hxx OcctJni_Viewer.hxx)
+set(SOURCE_FILES OcctJni_MsgPrinter.cxx OcctJni_Viewer.cxx)
+
+set (anOcctLibs
+ TKernel TKMath TKG2d TKG3d TKGeomBase TKBRep TKGeomAlgo TKTopAlgo TKShHealing TKMesh
+ # exchange
+ TKPrim TKBO TKBool TKFillet TKOffset
+ TKXSBase
+ TKSTL
+ TKIGES
+ TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP
+ # OCCT Visualization
+ TKService TKHLR TKV3d TKOpenGles
+)
+
+set(aLibDeps "")
+
+# OCCT libraries
+include_directories(${OCCT_ROOT}/inc)
+foreach(anOcctLib ${anOcctLibs})
+ add_library(lib_${anOcctLib} SHARED IMPORTED)
+ set_target_properties(lib_${anOcctLib} PROPERTIES IMPORTED_LOCATION ${OCCT_ROOT}/libs/${ANDROID_ABI}/lib${anOcctLib}.so)
+ list(APPEND aLibDeps lib_${anOcctLib})
+endforeach()
+
+# FreeType
+add_library(lib_FreeType SHARED IMPORTED)
+set_target_properties(lib_FreeType PROPERTIES IMPORTED_LOCATION ${FREETYPE_ROOT}/libs/${ANDROID_ABI}/libfreetype.so)
+list(APPEND aLibDeps lib_FreeType)
+
+# FreeImage - uncomment, if OCCT was built with FreeImage
+#add_library(lib_FreeImage SHARED IMPORTED)
+#set_target_properties(lib_FreeImage PROPERTIES IMPORTED_LOCATION ${FREETYPE_ROOT}/libs/${ANDROID_ABI}/libfreeimage.so)
+#list(APPEND aLibDeps lib_FreeImage)
+
+# system libraries
+list(APPEND aLibDeps EGL GLESv2 log android)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -frtti -fexceptions -fpermissive")
+
+add_library(TKJniSample SHARED ${SOURCE_FILES})
+target_link_libraries(TKJniSample ${aLibDeps})
diff --git a/app/src/main/jni/OcctJni_MsgPrinter.cxx b/app/src/main/jni/OcctJni_MsgPrinter.cxx
new file mode 100644
index 0000000..2934c97
--- /dev/null
+++ b/app/src/main/jni/OcctJni_MsgPrinter.cxx
@@ -0,0 +1,79 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+#include "OcctJni_MsgPrinter.hxx"
+
+#include
+#include
+
+#include
+
+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_AsciiString& theString,
+ const Message_Gravity theGravity) 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->CallVoidMethod (myJObj, myJMet, aJStr);
+ myJEnv->DeleteLocalRef (aJStr);
+}
diff --git a/app/src/main/jni/OcctJni_MsgPrinter.hxx b/app/src/main/jni/OcctJni_MsgPrinter.hxx
new file mode 100644
index 0000000..a52b03b
--- /dev/null
+++ b/app/src/main/jni/OcctJni_MsgPrinter.hxx
@@ -0,0 +1,56 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+#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
+{
+ DEFINE_STANDARD_RTTIEXT(OcctJni_MsgPrinter, Message_Printer)
+public:
+
+ //! Default constructor
+ OcctJni_MsgPrinter (JNIEnv* theJEnv,
+ jobject theJObj);
+
+ //! Destructor.
+ ~OcctJni_MsgPrinter();
+
+protected:
+
+ //! Main printing method
+ virtual void send (const TCollection_AsciiString& theString,
+ const Message_Gravity theGravity) const override;
+
+private:
+
+ JNIEnv* myJEnv;
+ jobject myJObj;
+ jmethodID myJMet;
+
+};
+
+#endif // OcctJni_MsgPrinter_H
diff --git a/app/src/main/jni/OcctJni_Viewer.cxx b/app/src/main/jni/OcctJni_Viewer.cxx
new file mode 100644
index 0000000..59ec8e9
--- /dev/null
+++ b/app/src/main/jni/OcctJni_Viewer.cxx
@@ -0,0 +1,718 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+#include "OcctJni_Viewer.hxx"
+#include "OcctJni_MsgPrinter.hxx"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+#include
+
+// =======================================================================
+// function : OcctJni_Viewer
+// purpose :
+// =======================================================================
+OcctJni_Viewer::OcctJni_Viewer (float theDispDensity)
+: myDevicePixelRatio (theDispDensity),
+ myIsJniMoreFrames (false)
+{
+ SetTouchToleranceScale (theDispDensity);
+#ifndef NDEBUG
+ // Register printer for logging messages into global Android log.
+ // Should never be used in production (or specify higher gravity for logging only failures).
+ Handle(Message_Messenger) aMsgMgr = Message::DefaultMessenger();
+ aMsgMgr->RemovePrinters (STANDARD_TYPE (Message_PrinterSystemLog));
+ aMsgMgr->AddPrinter (new Message_PrinterSystemLog ("OcctJni_Viewer"));
+#endif
+}
+
+// ================================================================
+// Function : dumpGlInfo
+// Purpose :
+// ================================================================
+void OcctJni_Viewer::dumpGlInfo (bool theIsBasic)
+{
+ TColStd_IndexedDataMapOfStringString aGlCapsDict;
+ myView->DiagnosticInformation (aGlCapsDict, Graphic3d_DiagnosticInfo_Basic); //theIsBasic ? Graphic3d_DiagnosticInfo_Basic : Graphic3d_DiagnosticInfo_Complete);
+ if (theIsBasic)
+ {
+ TCollection_AsciiString aViewport;
+ aGlCapsDict.FindFromKey ("Viewport", aViewport);
+ aGlCapsDict.Clear();
+ aGlCapsDict.Add ("Viewport", aViewport);
+ }
+ aGlCapsDict.Add ("Display scale", TCollection_AsciiString(myDevicePixelRatio));
+
+ // beautify output
+ {
+ TCollection_AsciiString* aGlVer = aGlCapsDict.ChangeSeek ("GLversion");
+ TCollection_AsciiString* aGlslVer = aGlCapsDict.ChangeSeek ("GLSLversion");
+ if (aGlVer != NULL
+ && aGlslVer != NULL)
+ {
+ *aGlVer = *aGlVer + " [GLSL: " + *aGlslVer + "]";
+ aGlslVer->Clear();
+ }
+ }
+
+ TCollection_AsciiString anInfo;
+ for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aGlCapsDict); aValueIter.More(); aValueIter.Next())
+ {
+ if (!aValueIter.Value().IsEmpty())
+ {
+ if (!anInfo.IsEmpty())
+ {
+ anInfo += "\n";
+ }
+ anInfo += aValueIter.Key() + ": " + aValueIter.Value();
+ }
+ }
+
+ Message::SendWarning (anInfo);
+}
+
+// =======================================================================
+// 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::SendFail ("Error: No active EGL context!");
+ 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::SendFail ("Error: EGL does not provide compatible configurations!");
+ release();
+ return false;
+ }
+
+ if (!myViewer.IsNull())
+ {
+ Handle(OpenGl_GraphicDriver) aDriver = Handle(OpenGl_GraphicDriver)::DownCast (myViewer->Driver());
+ Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::DownCast (myView->Window());
+ if (!aDriver->InitEglContext (anEglDisplay, anEglContext, anEglConfig))
+ {
+ Message::SendFail ("Error: OpenGl_GraphicDriver can not be initialized!");
+ release();
+ return false;
+ }
+
+ aWindow->SetSize (aWidth, aHeight);
+ myView->SetWindow (aWindow, (Aspect_RenderingContext )anEglContext);
+ dumpGlInfo (true);
+ return true;
+ }
+
+ Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (NULL, Standard_False);
+ aDriver->ChangeOptions().buffersNoSwap = true;
+ aDriver->ChangeOptions().buffersOpaqueAlpha = true;
+ aDriver->ChangeOptions().useSystemBuffer = false;
+ if (!aDriver->InitEglContext (anEglDisplay, anEglContext, anEglConfig))
+ {
+ Message::SendFail ("Error: OpenGl_GraphicDriver can not be initialized!");
+ release();
+ return false;
+ }
+
+ myTextStyle = new Prs3d_TextAspect();
+ myTextStyle->SetFont (Font_NOF_ASCII_MONO);
+ myTextStyle->SetHeight (12);
+ myTextStyle->Aspect()->SetColor (Quantity_NOC_GRAY95);
+ myTextStyle->Aspect()->SetColorSubTitle (Quantity_NOC_BLACK);
+ myTextStyle->Aspect()->SetDisplayType (Aspect_TODT_SHADOW);
+ myTextStyle->Aspect()->SetTextFontAspect (Font_FA_Bold);
+ myTextStyle->Aspect()->SetTextZoomable (false);
+ myTextStyle->SetHorizontalJustification (Graphic3d_HTA_LEFT);
+ myTextStyle->SetVerticalJustification (Graphic3d_VTA_BOTTOM);
+
+ // create viewer
+ myViewer = new V3d_Viewer (aDriver);
+ myViewer->SetDefaultBackgroundColor (Quantity_NOC_BLACK);
+ myViewer->SetDefaultLights();
+ myViewer->SetLightOn();
+
+ // create AIS context
+ myContext = new AIS_InteractiveContext (myViewer);
+ myContext->SetPixelTolerance (int(myDevicePixelRatio * 6.0)); // increase tolerance and adjust to hi-dpi screens
+ myContext->SetDisplayMode (AIS_Shaded, false);
+
+ Handle(Aspect_NeutralWindow) aWindow = new Aspect_NeutralWindow();
+ aWindow->SetSize (aWidth, aHeight);
+ myView = myViewer->CreateView();
+ myView->SetImmediateUpdate (false);
+ myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
+ myView->ChangeRenderingParams().ToShowStats = true;
+ myView->ChangeRenderingParams().CollectedStats = (Graphic3d_RenderingParams::PerfCounters ) (Graphic3d_RenderingParams::PerfCounters_FrameRate | Graphic3d_RenderingParams::PerfCounters_Triangles);
+ myView->ChangeRenderingParams().StatsTextAspect = myTextStyle->Aspect();
+ myView->ChangeRenderingParams().StatsTextHeight = (int )myTextStyle->Height();
+
+ myView->SetWindow (aWindow, (Aspect_RenderingContext )anEglContext);
+ dumpGlInfo (false);
+ //myView->TriedronDisplay (Aspect_TOTP_RIGHT_LOWER, Quantity_NOC_WHITE, 0.08 * myDevicePixelRatio, 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::SendFail ("Resize failed - view is unavailable");
+ return;
+ }
+
+ Handle(OpenGl_GraphicDriver) aDriver = Handle(OpenGl_GraphicDriver)::DownCast (myViewer->Driver());
+ Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::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);
+ dumpGlInfo (true);
+}
+
+// =======================================================================
+// function : initContent
+// purpose :
+// =======================================================================
+void OcctJni_Viewer::initContent()
+{
+ myContext->RemoveAll (Standard_False);
+
+ if (myViewCube.IsNull())
+ {
+ myViewCube = new AIS_ViewCube();
+ {
+ // setup view cube size
+ static const double THE_CUBE_SIZE = 60.0;
+ myViewCube->SetSize (myDevicePixelRatio * THE_CUBE_SIZE, false);
+ myViewCube->SetBoxFacetExtension (myViewCube->Size() * 0.15);
+ myViewCube->SetAxesPadding (myViewCube->Size() * 0.10);
+ myViewCube->SetFontHeight (THE_CUBE_SIZE * 0.16);
+ }
+ // presentation parameters
+ myViewCube->SetTransformPersistence (new Graphic3d_TransformPers (Graphic3d_TMF_TriedronPers, Aspect_TOTP_RIGHT_LOWER, Graphic3d_Vec2i (200, 200)));
+ myViewCube->Attributes()->SetDatumAspect (new Prs3d_DatumAspect());
+ myViewCube->Attributes()->DatumAspect()->SetTextAspect (myTextStyle);
+ // animation parameters
+ myViewCube->SetViewAnimation (myViewAnimation);
+ myViewCube->SetFixedAnimationLoop (false);
+ myViewCube->SetAutoStartAnimation (true);
+ }
+ myContext->Display (myViewCube, 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::SendInfo (TCollection_AsciiString() + "Presentation computed in " + aTimer.ElapsedTime() + " seconds");
+}
+
+//! 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::SendFail ("Error: IGES reader, computation error");
+ return aShape;
+ }
+
+ if (aReadStatus != IFSelect_RetDone)
+ {
+ Message::SendFail ("Error: IGES reader, bad file format");
+ 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::SendFail ("Error: IGES reader, no shapes has been found");
+ return aShape;
+ }
+ return aReader.OneShape();
+}
+
+//! 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::SendFail ("Error: STEP reader, computation error");
+ return TopoDS_Shape();
+ }
+
+ if (aReadStatus != IFSelect_RetDone)
+ {
+ Message::SendFail ("Error: STEP reader, bad file format");
+ return TopoDS_Shape();
+ }
+ else if (aReader.NbRootsForTransfer() <= 0)
+ {
+ Message::SendFail ("Error: STEP reader, shape is empty");
+ return TopoDS_Shape();
+ }
+
+ // now perform the translation
+ aReader.TransferRoots();
+ return aReader.OneShape();
+}
+
+//! Load shape from STL file
+static TopoDS_Shape loadSTL (const TCollection_AsciiString& thePath)
+{
+ Handle(Poly_Triangulation) aTri = RWStl::ReadFile (thePath.ToCString());
+ TopoDS_Face aFace;
+ BRep_Builder().MakeFace (aFace, aTri);
+ return aFace;
+}
+
+// =======================================================================
+// function : open
+// purpose :
+// =======================================================================
+bool OcctJni_Viewer::open (const TCollection_AsciiString& thePath)
+{
+ myShape.Nullify();
+ if (!myContext.IsNull())
+ {
+ myContext->RemoveAll (Standard_False);
+ if (!myViewCube.IsNull())
+ {
+ myContext->Display (myViewCube, false);
+ }
+ }
+ if (thePath.IsEmpty())
+ {
+ return false;
+ }
+
+ OSD_Timer aTimer;
+ aTimer.Start();
+ TCollection_AsciiString aFileName, aFormatStr;
+ OSD_Path::FileNameAndExtension (thePath, aFileName, aFormatStr);
+ 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 == "stl")
+ {
+ aShape = loadSTL (thePath);
+ }
+ else
+ // if (aFormatStr == "brep"
+ // || aFormatStr == "rle")
+ {
+ BRep_Builder aBuilder;
+ if (!BRepTools::Read (aShape, thePath.ToCString(), aBuilder))
+ {
+ Message::SendInfo (TCollection_AsciiString() + "Error: file '" + thePath + "' can not be opened");
+ return false;
+ }
+ }
+ if (aShape.IsNull())
+ {
+ return false;
+ }
+ aTimer.Stop();
+ Message::SendInfo (TCollection_AsciiString() + "File '" + thePath + "' loaded in " + aTimer.ElapsedTime() + " seconds");
+
+ 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::SendInfo (TCollection_AsciiString() + "Presentation computed in " + aTimer.ElapsedTime() + " seconds");
+ return true;
+}
+
+// =======================================================================
+// function : saveSnapshot
+// purpose :
+// =======================================================================
+bool OcctJni_Viewer::saveSnapshot (const TCollection_AsciiString& thePath,
+ int theWidth,
+ int theHeight)
+{
+ if (myContext.IsNull()
+ || thePath.IsEmpty())
+ {
+ Message::SendFail ("Image dump failed - view is unavailable");
+ return false;
+ }
+
+ if (theWidth < 1
+ || theHeight < 1)
+ {
+ myView->Window()->Size (theWidth, theHeight);
+ }
+ if (theWidth < 1
+ || theHeight < 1)
+ {
+ Message::SendFail ("Image dump failed - view is unavailable");
+ return false;
+ }
+
+ Image_AlienPixMap anImage;
+ if (!anImage.InitTrash (Image_Format_BGRA, theWidth, theHeight))
+ {
+ Message::SendFail (TCollection_AsciiString() + "RGBA image " + theWidth + "x" + theHeight + " allocation failed");
+ return false;
+ }
+
+ if (!myView->ToPixMap (anImage, theWidth, theHeight, Graphic3d_BT_RGBA))
+ {
+ Message::SendFail (TCollection_AsciiString() + "View dump to the image " + theWidth + "x" + theHeight + " failed");
+ }
+ if (!anImage.Save (thePath))
+ {
+ Message::SendFail (TCollection_AsciiString() + "Image saving to path '" + thePath + "' failed");
+ return false;
+ }
+ Message::SendInfo (TCollection_AsciiString() + "View " + theWidth + "x" + theHeight + " dumped to image '" + thePath + "'");
+ return true;
+}
+
+// ================================================================
+// Function : handleViewRedraw
+// Purpose :
+// ================================================================
+void OcctJni_Viewer::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
+ const Handle(V3d_View)& theView)
+{
+ AIS_ViewController::handleViewRedraw (theCtx, theView);
+ myIsJniMoreFrames = myToAskNextFrame;
+}
+
+// =======================================================================
+// function : redraw
+// purpose :
+// =======================================================================
+bool OcctJni_Viewer::redraw()
+{
+ if (myView.IsNull())
+ {
+ return false;
+ }
+
+ // handle user input
+ myIsJniMoreFrames = false;
+ myView->InvalidateImmediate();
+ FlushViewEvents (myContext, myView, true);
+ return myIsJniMoreFrames;
+}
+
+// =======================================================================
+// function : fitAll
+// purpose :
+// =======================================================================
+void OcctJni_Viewer::fitAll()
+{
+ if (myView.IsNull())
+ {
+ return;
+ }
+
+ myView->FitAll (0.01, Standard_False);
+ myView->Invalidate();
+}
+
+#define jexp extern "C" JNIEXPORT
+
+jexp jlong JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppCreate (JNIEnv* theEnv,
+ jobject theObj,
+ jfloat theDispDensity)
+{
+ return jlong(new OcctJni_Viewer (theDispDensity));
+}
+
+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 jboolean JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppRedraw (JNIEnv* theEnv,
+ jobject theObj,
+ jlong theCppPtr)
+{
+ return ((OcctJni_Viewer* )theCppPtr)->redraw() ? JNI_TRUE : JNI_FALSE;
+}
+
+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_cppAddTouchPoint (JNIEnv* theEnv,
+ jobject theObj,
+ jlong theCppPtr,
+ jint theId,
+ jfloat theX,
+ jfloat theY)
+{
+ ((OcctJni_Viewer* )theCppPtr)->AddTouchPoint (theId, Graphic3d_Vec2d (theX, theY));
+}
+
+jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppUpdateTouchPoint (JNIEnv* theEnv,
+ jobject theObj,
+ jlong theCppPtr,
+ jint theId,
+ jfloat theX,
+ jfloat theY)
+{
+ ((OcctJni_Viewer* )theCppPtr)->UpdateTouchPoint (theId, Graphic3d_Vec2d (theX, theY));
+}
+
+jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppRemoveTouchPoint (JNIEnv* theEnv,
+ jobject theObj,
+ jlong theCppPtr,
+ jint theId)
+{
+ ((OcctJni_Viewer* )theCppPtr)->RemoveTouchPoint (theId);
+}
+
+jexp void JNICALL Java_com_opencascade_jnisample_OcctJniRenderer_cppSelectInViewer (JNIEnv* theEnv,
+ jobject theObj,
+ jlong theCppPtr,
+ jfloat theX,
+ jfloat theY)
+{
+ ((OcctJni_Viewer* )theCppPtr)->SelectInViewer (Graphic3d_Vec2i ((int )theX, (int )theY));
+}
+
+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/app/src/main/jni/OcctJni_Viewer.hxx b/app/src/main/jni/OcctJni_Viewer.hxx
new file mode 100644
index 0000000..f56e86e
--- /dev/null
+++ b/app/src/main/jni/OcctJni_Viewer.hxx
@@ -0,0 +1,99 @@
+// Copyright (c) 2014-2021 OPEN CASCADE SAS
+//
+// This file is part of the examples of the Open CASCADE Technology software library.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+
+#include
+#include
+#include
+#include
+#include
+
+class AIS_ViewCube;
+
+//! Main C++ back-end for activity.
+class OcctJni_Viewer : public AIS_ViewController
+{
+
+public:
+
+ //! Empty constructor
+ OcctJni_Viewer (float theDispDensity);
+
+ //! 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.
+ //! Returns TRUE if more frames should be requested.
+ bool redraw();
+
+ //! Move camera
+ void setProj (V3d_TypeOfOrientation theProj)
+ {
+ if (myView.IsNull())
+ {
+ return;
+ }
+
+ myView->SetProj (theProj);
+ myView->Invalidate();
+ }
+
+ //! Fit All.
+ void fitAll();
+
+protected:
+
+ //! Reset viewer content.
+ void initContent();
+
+ //! Print information about OpenGL ES context.
+ void dumpGlInfo (bool theIsBasic);
+
+ //! Handle redraw.
+ virtual void handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
+ const Handle(V3d_View)& theView) override;
+
+protected:
+
+ Handle(V3d_Viewer) myViewer;
+ Handle(V3d_View) myView;
+ Handle(AIS_InteractiveContext) myContext;
+ Handle(Prs3d_TextAspect) myTextStyle; //!< text style for OSD elements
+ Handle(AIS_ViewCube) myViewCube; //!< view cube object
+ TopoDS_Shape myShape;
+ float myDevicePixelRatio; //!< device pixel ratio for handling high DPI displays
+ bool myIsJniMoreFrames; //!< need more frame flag
+
+};
diff --git a/app/src/main/res/drawable-hdpi/close_l.png b/app/src/main/res/drawable-hdpi/close_l.png
new file mode 100644
index 0000000..0125c4a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/close_l.png differ
diff --git a/app/src/main/res/drawable-hdpi/close_p.png b/app/src/main/res/drawable-hdpi/close_p.png
new file mode 100644
index 0000000..b5ce8bd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/close_p.png differ
diff --git a/app/src/main/res/drawable-hdpi/fit.png b/app/src/main/res/drawable-hdpi/fit.png
new file mode 100644
index 0000000..70daee1
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/fit.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..d27ba82
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-hdpi/info.png b/app/src/main/res/drawable-hdpi/info.png
new file mode 100644
index 0000000..88d27c8
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/info.png differ
diff --git a/app/src/main/res/drawable-hdpi/info_image.png b/app/src/main/res/drawable-hdpi/info_image.png
new file mode 100644
index 0000000..bac9bea
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/info_image.png differ
diff --git a/app/src/main/res/drawable-hdpi/message.png b/app/src/main/res/drawable-hdpi/message.png
new file mode 100644
index 0000000..a3dc8cc
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/message.png differ
diff --git a/app/src/main/res/drawable-hdpi/open.png b/app/src/main/res/drawable-hdpi/open.png
new file mode 100644
index 0000000..68e5265
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/open.png differ
diff --git a/app/src/main/res/drawable-hdpi/open_l.png b/app/src/main/res/drawable-hdpi/open_l.png
new file mode 100644
index 0000000..6069ce3
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/open_l.png differ
diff --git a/app/src/main/res/drawable-hdpi/open_p.png b/app/src/main/res/drawable-hdpi/open_p.png
new file mode 100644
index 0000000..c0898e6
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/open_p.png differ
diff --git a/app/src/main/res/drawable-hdpi/proj_back.png b/app/src/main/res/drawable-hdpi/proj_back.png
new file mode 100644
index 0000000..77ab199
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proj_back.png differ
diff --git a/app/src/main/res/drawable-hdpi/proj_bottom.png b/app/src/main/res/drawable-hdpi/proj_bottom.png
new file mode 100644
index 0000000..b0bb012
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proj_bottom.png differ
diff --git a/app/src/main/res/drawable-hdpi/proj_front.png b/app/src/main/res/drawable-hdpi/proj_front.png
new file mode 100644
index 0000000..b93d4d3
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proj_front.png differ
diff --git a/app/src/main/res/drawable-hdpi/proj_left.png b/app/src/main/res/drawable-hdpi/proj_left.png
new file mode 100644
index 0000000..bce95df
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proj_left.png differ
diff --git a/app/src/main/res/drawable-hdpi/proj_right.png b/app/src/main/res/drawable-hdpi/proj_right.png
new file mode 100644
index 0000000..8cdb338
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proj_right.png differ
diff --git a/app/src/main/res/drawable-hdpi/proj_top.png b/app/src/main/res/drawable-hdpi/proj_top.png
new file mode 100644
index 0000000..4ad098f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/proj_top.png differ
diff --git a/app/src/main/res/drawable-hdpi/view.png b/app/src/main/res/drawable-hdpi/view.png
new file mode 100644
index 0000000..76ce879
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/view.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..4b86dbf
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..cd79bea
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..a34301f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e7f3ebf
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/id.xml b/app/src/main/res/values/id.xml
new file mode 100644
index 0000000..56bde94
--- /dev/null
+++ b/app/src/main/res/values/id.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..039a88f
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,34 @@
+
+
+ OpenCASCADE JNI Kotlin Sample
+ #484848
+ #0099CC
+ #66252525
+
+ - .png
+ - .jpg
+
+
+ - .brep
+ - .rle
+ - .iges
+ - .igs
+ - .step
+ - .stp
+
+ wireframe/shading
+ color
+ material
+ transparency
+ show/hide hidden lines
+
+ OpenCASCADE JNI Kotlin Sample
+ Simple viewer for BREP, STEP and IGES files.
+ Driven by Open CASCADE Technology %d.%d.%d.
+ Copyright 2014-2021 OPEN CASCADE SAS.
+
+ https://dev.opencascade.org
+ ]]>
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7252192
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext.kotlin_version = '1.4.32'
+ repositories {
+ jcenter()
+ google()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.0.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ google()
+ }
+}
diff --git a/gradle.properties.template b/gradle.properties.template
new file mode 100644
index 0000000..2cf7323
--- /dev/null
+++ b/gradle.properties.template
@@ -0,0 +1,5 @@
+# customized paths
+OCCT_ROOT=c\:/android/occt-dev-android
+FREETYPE_ROOT=c\:/android/freetype-2.7.1-android
+# in case if OCCT was built with FreeImage
+#FREEIMAGE_ROOT=c\:/android/freeimage-3.17-android
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'