# -*- coding: utf-8 -*-
import os
import shutil
import click
import zipfile
import requests
import webbrowser
from io import BytesIO
from importlib.metadata import version
@click.group()
@click.version_option(version=version('kanapy'))
@click.pass_context
def main(ctx):
pass
@main.command(name='gui')
@click.pass_context
def gui(ctx):
"""
Start Kanapy's graphical user interface (experimental alpha version)
This function initializes and launches the Kanapy GUI using Tkinter.
It creates a main application window with multiple tabs for different
RVE generation modes, such as particle-based and cuboid-based RVEs.
Parameters
----------
ctx : object
Execution context (reserved for CLI or application integration)
Notes
-----
- This GUI version is experimental and intended for testing only
- Requires `matplotlib` and `tkinter` libraries
"""
import matplotlib.pyplot as plt
import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk
from .gui import particle_rve, cuboid_rve
app = tk.Tk()
app.title("RVE_Generation")
screen_width = app.winfo_screenwidth()
screen_height = app.winfo_screenheight()
plt.rcParams['figure.dpi'] = screen_height / 19 # height stats_plot: 9, height voxel_plot: 6, margin: 4
window_width = int(screen_width * 0.6)
window_height = int(screen_height * 0.8)
x_coordinate = int((screen_width / 2) - (window_width / 2))
y_coordinate = 0 # int((screen_height / 2) - (window_height / 2))
app.geometry(f"{window_width}x{window_height}+{x_coordinate}+{y_coordinate}")
notebook = ttk.Notebook(app)
notebook.pack(fill='both', expand=True)
style = ttk.Style(app)
default_font = tkFont.Font(family="Helvetica", size=12, weight="bold")
style.configure('TNotebook.Tab', font=('Helvetica', '12', "bold"))
style.configure('TButton', font=default_font)
""" Start main loop """
prve = particle_rve(app, notebook) # First tab: Particle-based grains
crve = cuboid_rve(app, notebook) # Second tab: Cuboid grains
#erve = ebsd_rve() # Third tab: EBSDbased RVE
app.mainloop()
@main.command(name='runTests')
@click.option('--no_texture', default=False)
@click.pass_context
def tests(ctx, no_texture: bool):
"""
Run Kanapy's internal unittests
Executes the built-in test suite for Kanapy using pytest.
Depending on the `no_texture` flag, it either runs a subset of tests
or all available tests in the `tests` directory.
Parameters
----------
ctx : object
Execution context (reserved for CLI or internal use)
no_texture : bool
If True, run tests excluding texture-related modules;
if False, run the full test suite
Notes
-----
- Must be executed from the root directory of the Kanapy installation
- Temporary dump files generated during testing are automatically removed
"""
click.echo('Will only work in root directory of kanapy installation.')
cwd = os.getcwd()
if no_texture:
#t1 = "{0}/tests/test_collide_detect_react.py".format(cwd)
t2 = "{0}/tests/test_entities.py".format(cwd)
t3 = "{0}/tests/test_input_output.py".format(cwd)
t4 = "{0}/tests/test_packing.py".format(cwd)
t5 = "{0}/tests/test_voxelization.py".format(cwd)
os.system(f"pytest {t2} {t3} {t4} {t5} -v")
else:
os.system("pytest {0}/tests/ -v".format(cwd))
shutil.rmtree(os.path.join(cwd, "dump_files"))
click.echo('')
@main.command(name='readDocs')
@click.pass_context
def docs(ctx):
"""
Open the Kanapy documentation webpage
Launches the default web browser and navigates to the official
Kanapy documentation page hosted on GitHub Pages.
Parameters
----------
ctx : object
Execution context (reserved for CLI or internal use)
"""
webbrowser.open("https://icams.github.io/Kanapy/")
@main.command(name='copyExamples')
@click.pass_context
def download_subdir(ctx):
"""
Download example files from Kanapy's GitHub repository
Fetches the latest Kanapy repository archive from GitHub,
extracts the `examples` subdirectory, and saves it to the local
working directory as `kanapy_examples`.
Parameters
----------
ctx : object
Execution context (reserved for CLI or internal use)
Notes
-----
- Requires an active internet connection
- Downloads from the `master` branch of the Kanapy repository
- Automatically creates the output directory if it does not exist
"""
zip_url = f"https://github.com/ICAMS/kanapy/archive/refs/heads/master.zip"
output_dir = os.path.join(os.getcwd(), 'kanapy_examples')
subdir_prefix = "Kanapy-master/examples/"
click.echo(f"Downloading ZIP from: {zip_url}")
r = requests.get(zip_url)
r.raise_for_status()
with zipfile.ZipFile(BytesIO(r.content)) as zf:
members = [f for f in zf.namelist() if f.startswith(subdir_prefix)]
if not members:
click.echo(f"Error: Subdirectory 'examples' not found in branch 'master'.", err=True)
return
for member in members:
rel_path = os.path.relpath(member, subdir_prefix)
if not rel_path or member.endswith("/"):
continue
target_path = os.path.join(output_dir, rel_path)
os.makedirs(os.path.dirname(target_path), exist_ok=True)
with open(target_path, "wb") as out_file:
out_file.write(zf.read(member))
click.echo(f"Extracted 'examples' from ICAMS/kanapy to '{output_dir}/'")
@main.command(name='setupMTEX')
@click.pass_context
def setup_mtex(ctx):
"""
Start the MATLAB engine and initialize MTEX
Launches the MATLAB engine from Python and sets up the
MTEX toolbox environment for crystallographic computations.
Parameters
----------
ctx : object
Execution context (reserved for CLI or internal use)
Notes
-----
- Requires MATLAB and the MTEX toolbox to be installed
- Calls `setPaths()` to configure MATLAB path settings
"""
setPaths()
[docs]
def chkVersion(matlab):
"""
Read the installed MATLAB version from a version string
Parses the given MATLAB version string to extract the release year
(e.g., 'R2023a' → 2023). If the version cannot be determined, returns None.
Parameters
----------
matlab : str
String containing MATLAB version information
Returns
-------
int or None
MATLAB release year if successfully parsed, otherwise None
"""
ind = matlab.find('R20')
if ind < 0:
version = None
else: # Find the matlab version available in the system
try:
version = int(matlab[ind+1:ind+5])
click.echo(f'Detected Matlab version R{version}')
except:
version = None
return version
[docs]
def setPaths():
"""
Start the MATLAB engine and initialize the MTEX environment
Checks if the MATLAB engine for Python is installed and installs it if missing.
Then initializes the MATLAB engine and MTEX toolbox for use with Kanapy.
Raises
------
ModuleNotFoundError
If `kanapy-mtex` or MATLAB are not installed,
or if the MATLAB engine installation fails
Notes
-----
- Requires MATLAB 2025a or later
- Automatically installs the `matlabengine` package if not found
- Configures Kanapy for texture analysis by running `init_engine.py`
"""
try:
from kanapy_mtex import ROOT_DIR
except:
raise ModuleNotFoundError('This function in only evailable if kanapy-mtex and Matlab are installed.')
# check if Matlab Engine library is already installed
try:
import matlab.engine
click.echo('Using existing matlab.engine. Please update if required.')
except:
# if not, install matlab engine
click.echo('Installing matlab.engine...')
# ind = userpath1.find('bin') # remove bin/matlab from matlab path
# path = os.path.join(userpath1[0:ind], 'extern', 'engines', 'python') # complete path to matlab engine
# os.chdir(path)
res = os.system('python -m pip install matlabengine==25.1.2')
if res != 0:
raise ModuleNotFoundError('Error in installing matlab.engine. This feature requires Matlab 2025a or above.')
# initalize matlab engine and MTEX for kanapy
os.chdir(ROOT_DIR)
os.system('python init_engine.py')
click.echo('')
click.echo('Kanapy is now configured for texture analysis!\n')
[docs]
def start():
main(obj={})
if __name__ == '__main__':
start()