mirror of
https://github.com/ToxicCrack/PrintABrick.git
synced 2025-05-16 20:30:09 -07:00
397 lines
13 KiB
PHP
397 lines
13 KiB
PHP
<?php
|
|
|
|
namespace AppBundle\Service\Loader;
|
|
|
|
use AppBundle\Entity\LDraw\Alias;
|
|
use AppBundle\Entity\LDraw\Author;
|
|
use AppBundle\Entity\LDraw\Category;
|
|
use AppBundle\Entity\LDraw\Keyword;
|
|
use AppBundle\Entity\LDraw\Model;
|
|
use AppBundle\Entity\LDraw\Subpart;
|
|
use AppBundle\Exception\ConvertingFailedException;
|
|
use AppBundle\Exception\FileException;
|
|
use AppBundle\Exception\MissingContextException;
|
|
use AppBundle\Exception\ParseErrorException;
|
|
use AppBundle\Repository\LDraw\ModelRepository;
|
|
use AppBundle\Service\Stl\StlConverterService;
|
|
use AppBundle\Util\LDModelParser;
|
|
use AppBundle\Util\RelationMapper;
|
|
use League\Flysystem\Adapter\Local;
|
|
use League\Flysystem\Exception;
|
|
use League\Flysystem\Filesystem;
|
|
|
|
class ModelLoader extends BaseLoader
|
|
{
|
|
/**
|
|
* @var Filesystem
|
|
*/
|
|
private $ldrawLibraryContext;
|
|
|
|
/**
|
|
* @var Filesystem
|
|
*/
|
|
private $fileContext;
|
|
|
|
/**
|
|
* @var StlConverterService
|
|
*/
|
|
private $stlConverter;
|
|
|
|
/** @var LDModelParser */
|
|
private $ldModelParser;
|
|
|
|
/** @var RelationMapper */
|
|
private $relationMapper;
|
|
|
|
/** @var string */
|
|
private $LDLibraryUrl;
|
|
|
|
/** @var bool */
|
|
private $rewrite = false;
|
|
|
|
/**
|
|
* LDrawLoaderService constructor.
|
|
*
|
|
* @param StlConverterService $stlConverter
|
|
* @param RelationMapper $relationMapper
|
|
*/
|
|
public function __construct($stlConverter, $relationMapper)
|
|
{
|
|
$this->stlConverter = $stlConverter;
|
|
$this->relationMapper = $relationMapper;
|
|
$this->ldModelParser = new LDModelParser();
|
|
}
|
|
|
|
/**
|
|
* @param bool $rewrite
|
|
*/
|
|
public function setRewrite($rewrite)
|
|
{
|
|
$this->rewrite = $rewrite;
|
|
}
|
|
|
|
/**
|
|
* @param $ldrawLibrary
|
|
*/
|
|
public function setLDrawLibraryContext($ldrawLibrary)
|
|
{
|
|
try {
|
|
$adapter = new Local($ldrawLibrary);
|
|
$this->ldrawLibraryContext = new Filesystem($adapter);
|
|
$this->stlConverter->setLDrawLibraryContext($this->ldrawLibraryContext);
|
|
} catch (Exception $exception) {
|
|
$this->logger->error($exception->getMessage());
|
|
}
|
|
}
|
|
|
|
public function downloadLibrary($url)
|
|
{
|
|
$this->writeOutput([
|
|
'<fg=cyan>------------------------------------------------------------------------------</>',
|
|
'<fg=cyan>Downloading LDraw library</>',
|
|
'<fg=cyan>------------------------------------------------------------------------------</>',
|
|
]);
|
|
|
|
$libraryZip = $this->downloadFile($url);
|
|
|
|
$temp_dir = tempnam(sys_get_temp_dir(), 'printabrick.');
|
|
if (file_exists($temp_dir)) {
|
|
unlink($temp_dir);
|
|
}
|
|
mkdir($temp_dir);
|
|
|
|
$zip = new \ZipArchive();
|
|
if ($zip->open($libraryZip) != 'true') {
|
|
echo 'Error :- Unable to open the Zip File';
|
|
}
|
|
$zip->extractTo($temp_dir);
|
|
$zip->close();
|
|
unlink($libraryZip);
|
|
|
|
$this->writeOutput(['<info>LDraw libary downloaded</info>']);
|
|
|
|
return $temp_dir;
|
|
}
|
|
|
|
/**
|
|
* Load one model into database.
|
|
*
|
|
* @param string $file Absolute filepath of model to load
|
|
*/
|
|
public function loadOne($file)
|
|
{
|
|
if (!$this->ldrawLibraryContext) {
|
|
throw new MissingContextException('LDrawLibrary');
|
|
}
|
|
|
|
$connection = $this->em->getConnection();
|
|
try {
|
|
$connection->beginTransaction();
|
|
|
|
$this->getFileContext($file);
|
|
$this->loadModel($file);
|
|
|
|
$connection->commit();
|
|
} catch (\Exception $e) {
|
|
$connection->rollBack();
|
|
$this->logger->error($e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load all models from ldraw library context into database.
|
|
*/
|
|
public function loadAll()
|
|
{
|
|
if (!$this->ldrawLibraryContext) {
|
|
throw new MissingContextException('LDrawLibrary');
|
|
}
|
|
|
|
$this->writeOutput([
|
|
'<fg=cyan>------------------------------------------------------------------------------</>',
|
|
"<fg=cyan>Loading models from LDraw library:</> <comment>{$this->ldrawLibraryContext->getAdapter()->getPathPrefix()}</comment>",
|
|
'<fg=cyan>------------------------------------------------------------------------------</>',
|
|
]);
|
|
|
|
$files = $this->ldrawLibraryContext->listContents('parts', false);
|
|
$this->initProgressBar(count($files));
|
|
|
|
foreach ($files as $file) {
|
|
$this->progressBar->setMessage($file['basename']);
|
|
|
|
if ($file['type'] == 'file' && $file['extension'] == 'dat') {
|
|
$connection = $this->em->getConnection();
|
|
$connection->beginTransaction();
|
|
|
|
try {
|
|
$this->loadModel($this->ldrawLibraryContext->getAdapter()->getPathPrefix().$file['path']);
|
|
$connection->commit();
|
|
} catch (\Exception $exception) {
|
|
$connection->rollBack();
|
|
$this->logger->error($exception->getMessage());
|
|
}
|
|
$connection->close();
|
|
}
|
|
|
|
$this->progressBar->advance();
|
|
}
|
|
$this->progressBar->finish();
|
|
}
|
|
|
|
/**
|
|
* Load model entity and all related submodels into database while generating stl file of model.
|
|
* Uses LDView to convert LDraw .dat to .stl.
|
|
*
|
|
* @param $file
|
|
*
|
|
* @return Model|null|false
|
|
*/
|
|
public function loadModel($file)
|
|
{
|
|
/** @var ModelRepository $modelRepository */
|
|
$modelRepository = $this->em->getRepository(Model::class);
|
|
|
|
// Return model from database if rewrite is not enabled
|
|
if (!$this->rewrite && $model = $modelRepository->find(basename($file, '.dat'))) {
|
|
return $model;
|
|
}
|
|
|
|
try {
|
|
$modelArray = $this->ldModelParser->parse(file_get_contents($file));
|
|
} catch (ParseErrorException $e) {
|
|
$this->logger->error($e->getMessage(), [$file]);
|
|
|
|
return null;
|
|
} catch (FileException $e) {
|
|
$this->logger->error($e->getMessage(), [$file]);
|
|
|
|
return null;
|
|
}
|
|
|
|
// Check if model fulfills rules and should be loaded
|
|
if ($this->isModelIncluded($modelArray)) {
|
|
// Recursively load model parent (if any) and add model id as alias of parent
|
|
if (($parentId = $this->getParentId($modelArray)) && ($parentModelFile = $this->findSubmodelFile($parentId)) !== null) {
|
|
if ($parentModel = $this->loadModel($parentModelFile)) {
|
|
// Remove old model if ~moved to
|
|
if ($this->rewrite && ($old = $modelRepository->find($modelArray['id'])) != null) {
|
|
$modelRepository->delete($old);
|
|
}
|
|
|
|
$alias = $this->em->getRepository(Alias::class)->getOrCreate($modelArray['id'], $parentModel);
|
|
$parentModel->addAlias($alias);
|
|
|
|
$modelRepository->save($parentModel);
|
|
} else {
|
|
$this->logger->info('Model skipped. ', ['number' => $modelArray['id'], 'parent' => $modelArray['parent']]);
|
|
}
|
|
|
|
return $parentModel;
|
|
}
|
|
|
|
// Load model
|
|
$model = $modelRepository->getOrCreate($modelArray['id']);
|
|
|
|
// Recursively load models of subparts
|
|
if (isset($modelArray['subparts'])) {
|
|
foreach ($modelArray['subparts'] as $subpartId => $colors) {
|
|
foreach ($colors as $color => $count) {
|
|
// Try to find model of subpart
|
|
if (($subpartFile = $this->findSubmodelFile($subpartId)) !== null) {
|
|
$subModel = $this->loadModel($subpartFile);
|
|
if ($subModel) {
|
|
$subpart = $this->em->getRepository(Subpart::class)->getOrCreate($model, $subModel, $count, $color);
|
|
$model->addSubpart($subpart);
|
|
}
|
|
} else {
|
|
$this->logger->error('Subpart file not found', ['subpart' => $subpartId, 'model' => $modelArray]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add Keywords to model
|
|
if (isset($modelArray['keywords'])) {
|
|
foreach ($modelArray['keywords'] as $keyword) {
|
|
$keyword = $this->em->getRepository(Keyword::class)->getOrCreate(stripslashes(strtolower(trim($keyword))));
|
|
$model->addKeyword($keyword);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// update model only if newer version
|
|
if (!$model->getModified() || ($model->getModified() < $modelArray['modified'])) {
|
|
$stl = $this->stlConverter->datToStl($file, $this->rewrite)->getPath();
|
|
|
|
$model->setPath($stl);
|
|
|
|
$model
|
|
->setName($modelArray['name'])
|
|
->setCategory($this->em->getRepository(Category::class)->getOrCreate($modelArray['category']))
|
|
->setAuthor($this->em->getRepository(Author::class)->getOrCreate($modelArray['author']))
|
|
->setModified($modelArray['modified']);
|
|
|
|
$modelRepository->save($model);
|
|
}
|
|
} catch (ConvertingFailedException $e) {
|
|
$this->logger->error($e->getMessage());
|
|
|
|
return null;
|
|
}
|
|
|
|
return $model;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get parent id of model from alias_model.yml if defined or return parent id loaded by LDModelParser.
|
|
*
|
|
* Used to eliminate duplicites of models in library, that could not be determined automatically
|
|
*
|
|
* @param $modelArray
|
|
*
|
|
* @return string
|
|
*/
|
|
private function getParentId($modelArray)
|
|
{
|
|
if ($this->relationMapper->find($modelArray['id'], 'alias_model') !== $modelArray['id']) {
|
|
return $this->relationMapper->find($modelArray['id'], 'alias_model');
|
|
}
|
|
|
|
return $modelArray['parent'];
|
|
}
|
|
|
|
/**
|
|
* Find submodel file inside model context or inside LDraw library.
|
|
*
|
|
*
|
|
* LDraw.org Standards: File Format 1.0.2 (http://www.ldraw.org/article/218.html)
|
|
*
|
|
* "Sub-files can be located in the LDRAW\PARTS sub-directory, the LDRAW\P sub-directory, the LDRAW\MODELS sub-directory,
|
|
* the current file's directory, a path relative to one of these directories, or a full path may be specified. Sub-parts are typically
|
|
* stored in the LDRAW\PARTS\S sub-directory and so are referenced as s\subpart.dat, while hi-res primitives are stored in the
|
|
* LDRAW\P\48 sub-directory and so referenced as 48\hires.dat"
|
|
*
|
|
*
|
|
* @param $id
|
|
* @param Filesystem $context
|
|
*
|
|
* @return string
|
|
*/
|
|
private function findSubmodelFile($id)
|
|
{
|
|
// Replace "\" directory separator used inside ldraw model files with system directory separator
|
|
$filename = str_replace('\\', DIRECTORY_SEPARATOR, strtolower($id).'.dat');
|
|
|
|
// Try to find model in current file's directory
|
|
if ($this->fileContext && $this->fileContext->has($filename)) {
|
|
return $this->fileContext->getAdapter()->getPathPrefix().$filename;
|
|
} elseif ($this->ldrawLibraryContext) {
|
|
// Try to find model in current LDRAW\PARTS sub-directory
|
|
if ($this->ldrawLibraryContext->has('parts'.DIRECTORY_SEPARATOR.$filename)) {
|
|
return $this->ldrawLibraryContext->getAdapter()->getPathPrefix().'parts'.DIRECTORY_SEPARATOR.$filename;
|
|
}
|
|
// Try to find model in current LDRAW\P sub-directory
|
|
elseif ($this->ldrawLibraryContext->has('p'.DIRECTORY_SEPARATOR.$filename)) {
|
|
return $this->ldrawLibraryContext->getAdapter()->getPathPrefix().'p'.DIRECTORY_SEPARATOR.$filename;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get new filesystem context of current file.
|
|
*
|
|
* @param $file
|
|
*
|
|
* @return Filesystem
|
|
*/
|
|
private function getFileContext($file)
|
|
{
|
|
try {
|
|
$adapter = new Local(dirname($file));
|
|
$this->fileContext = new Filesystem($adapter);
|
|
} catch (Exception $exception) {
|
|
$this->logger->error($exception->getMessage());
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if model file should be loaded into database.
|
|
*
|
|
* @param $modelArray
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function isModelIncluded($modelArray)
|
|
{
|
|
// Do not include part primitives and subparts
|
|
if (in_array($modelArray['type'], ['48_Primitive', '8_Primitive', 'Primitive', 'Subpart'])) {
|
|
return false;
|
|
}
|
|
// Do not include Pov-RAY file
|
|
elseif ($modelArray['category'] == 'Pov-RAY') {
|
|
return false;
|
|
}
|
|
// Do not include sticker models
|
|
elseif ($modelArray['type'] == 'Sticker') {
|
|
$this->logger->info('Model skipped.', ['number' => $modelArray['id'], 'type' => $modelArray['type']]);
|
|
|
|
return false;
|
|
}
|
|
// Do not include models without permission to redistribute
|
|
elseif ($modelArray['license'] != 'Redistributable under CCAL version 2.0') {
|
|
$this->logger->info('Model skipped.', ['number' => $modelArray['id'], 'license' => $modelArray['license']]);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|