ktongue's picture
download
raw
7.96 kB
'''Module module_manager: define the ModuleManager and ModuleManagerError classes.'''
import os
import os.path as op
import sys
from typing import List, Dict, Tuple
import glob
import json
IGNORE_FILE_NAME = '.cqsignore'
class ModuleManager:
'''Manage CadQuery scripts (ie. Python modules)'''
def __init__(self, target: str, should_raise=False):
if op.isfile(target):
self.target_is_dir = False
self.modules_dir = op.abspath(op.dirname(target))
self.module_name = op.basename(target[:-3])
elif op.isdir(target):
self.target_is_dir = True
self.modules_dir = op.abspath(target)
self.module_name = None
else:
raise ModuleManagerError(f'No file or folder found at "{ target }".')
self.should_raise = should_raise
self.last_timestamp = 0
self.available_modules = {}
def init(self) -> None:
'''Initialize the module manager, in particular import the CadQuery Python module.'''
# pylint: disable=unused-import, import-outside-toplevel
print('Importing CadQuery...', end=' ', flush=True)
import cadquery
print('done.')
sys.path.insert(1, self.modules_dir)
self.available_modules = self.get_available_modules()
def get_available_modules(self) -> Dict[str, str]:
'''Returns a dictionary of available modules as module name: module path'''
ignored_files_path = self.get_ignored_files_path()
modules_path = []
for file_name in os.listdir(self.modules_dir):
file_path = op.join(self.modules_dir, file_name)
if op.isfile(file_path) \
and op.splitext(file_path)[1] == '.py' \
and file_path not in ignored_files_path:
modules_path.append(file_path)
return { op.basename(path)[:-3]: path for path in modules_path }
def get_ignored_files_path(self) -> List[str]:
'''Update the list of files ignored, based on the .cqsignore file.'''
ignore_file_path = op.join(self.modules_dir, IGNORE_FILE_NAME)
ignored_files_path = []
if op.isfile(ignore_file_path):
with open(ignore_file_path, encoding='utf-8') as ignore_file:
for line in ignore_file.readlines():
line = line.strip()
if line and not line.startswith('#'):
ignore = op.join(self.modules_dir, line)
ignored_files_path += glob.glob(ignore)
return ignored_files_path
def get_most_recent_module(self) -> Tuple[str, str]:
'''Return the last updated module info as a tuple containing its path and timestamp.'''
most_recent_module_path = ''
most_recent_timestamp = 0
if self.target_is_dir:
for module_path in self.available_modules.values():
timestamp = op.getmtime(module_path)
if timestamp > most_recent_timestamp:
most_recent_module_path = module_path
most_recent_timestamp = timestamp
else:
most_recent_module_path = op.join(self.modules_dir, self.module_name + '.py')
most_recent_timestamp = op.getmtime(most_recent_module_path)
return most_recent_module_path, most_recent_timestamp
def get_last_updated_file(self) -> str:
'''If a file has been updated since the last call of this function call,
return its path, otherwise return an empty string.'''
module_path, timestamp = self.get_most_recent_module()
last_updated = ''
if self.last_timestamp == 0:
self.last_timestamp = timestamp
if self.last_timestamp != timestamp:
print(f'File { module_path } updated.')
self.last_timestamp = timestamp
last_updated = module_path
return last_updated
def get_result(self):
'''Return a CQ assembly object composed of all models passed
to show_object and debug functions in the CadQuery script.'''
from cadquery.cqgi import CQModel
module_script = ''
with open(self.available_modules[self.module_name], encoding='utf-8') as module_file:
module_script = module_file.read()
model = CQModel(module_script)
result = model.build()
if not result.success:
raise ModuleManagerError('Error in model', result.exception)
return result
def get_assembly(self):
from cadquery import Assembly, Color
MODEL_COLOR_DEFAULT = Color(0.9, 0.7, 0.1)
MODEL_COLOR_DEBUG = Color(1 , 0 , 0 , 0.2)
build_result = self.get_result()
assembly = Assembly()
for counter, result in enumerate(build_result.results):
rgb = result.options.get('color', None)
alpha = result.options.get('alpha', None)
name = result.options.get('name' , None)
color = rgb if isinstance(rgb, Color) \
else Color(rgb) if isinstance(rgb, str) \
else Color(*rgb) if isinstance(rgb, tuple) \
else MODEL_COLOR_DEFAULT
if alpha:
color = Color(color.toTuple()[:3] + [ alpha ])
try:
assembly.add(result.shape, color=color, name=name)
except ValueError:
assembly.add(result.shape, color=color, name=f'{ name }_{ counter }')
for result in build_result.debugObjects:
assembly.add(result.shape, color=MODEL_COLOR_DEBUG)
if not assembly.children:
raise ValueError('nothing to show')
return assembly
def get_json_model(self) -> list:
'''Return the tesselated model of the assembly,
as a dictionnary usable by three-cad-viewer.'''
from jupyter_cadquery.cad_objects import to_assembly
from jupyter_cadquery.base import _tessellate_group
from jupyter_cadquery.utils import numpy_to_json
try:
jcq_assembly = to_assembly(*self.get_assembly().children)
assembly_tesselated = _tessellate_group(jcq_assembly)
assembly_json = numpy_to_json(assembly_tesselated)
except Exception as error:
raise ModuleManagerError('An error occured when tesselating the assembly.') from error
return json.loads(assembly_json)
def get_data(self) -> dict:
'''Return the data to send to the client, that includes the tesselated model.'''
data = {}
if self.module_name:
try:
data = {
'module_name': self.module_name,
'model': self.get_json_model(),
'source': ''
}
except ModuleManagerError as error:
if self.should_raise:
raise(error)
data = {
'error': error.message,
'stacktrace': error.stacktrace
}
return data
def get_ui_instance(self):
'''Retrieve the ui object imported in the CadQuery script, used to retrieve the model.'''
try:
return getattr(self.modules[self.module_name], 'ui')
except AttributeError as error:
raise ModuleManagerError('ui is not imported. '
+ 'Please add `from cq_server.ui import ui, show_object` '
+ 'at the begining of the script.') from error
class ModuleManagerError(Exception):
'''Error class used to define ModuleManager errors.'''
def __init__(self, message: str, stacktrace: str=''):
self.message = message
self.stacktrace = stacktrace
print('Module manager error: ' + message, file=sys.stderr)
if stacktrace:
print(stacktrace, file=sys.stderr)
super().__init__(self.message)

Xet Storage Details

Size:
7.96 kB
·
Xet hash:
6473512945e5586ed4d763a6f979649935e09c92e26766fa3ebb0be8e7741d83

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.