1
0
mirror of https://github.com/ToxicCrack/PrintABrick.git synced 2025-05-17 12:50:08 -07:00
2017-04-21 02:14:07 +02:00

335 lines
11 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\ParseErrorException;
use AppBundle\Service\LDViewService;
use AppBundle\Util\LDModelParser;
use AppBundle\Util\RelationMapper;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Exception;
use League\Flysystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
class ModelLoader extends BaseLoader
{
/**
* @var Filesystem
*/
private $ldrawLibraryContext;
/**
* @var LDViewService
*/
private $LDViewService;
/** @var LDModelParser */
private $ldModelParser;
/** @var RelationMapper */
private $relationMapper;
/** @var Finder */
private $finder;
private $rewite = false;
/**
* LDrawLoaderService constructor.
*
* @param LDViewService $LDViewService
* @param RelationMapper $relationMapper
*/
public function __construct($LDViewService, $relationMapper)
{
$this->LDViewService = $LDViewService;
$this->relationMapper = $relationMapper;
$this->ldModelParser = new LDModelParser();
$this->finder = new Finder();
}
/**
* @param bool $rewite
*/
public function setRewite($rewite)
{
$this->rewite = $rewite;
}
/**
* @param $ldrawLibrary
*/
public function setLDrawLibraryContext($ldrawLibrary)
{
try {
$adapter = new Local($ldrawLibrary);
$this->ldrawLibraryContext = new Filesystem($adapter);
$this->LDViewService->setLDrawLibraryContext($this->ldrawLibraryContext);
} catch (Exception $exception) {
$this->logger->error($exception->getMessage());
}
}
public function loadOne($file)
{
$connection = $this->em->getConnection();
try {
$connection->beginTransaction();
$this->loadModel($file);
$connection->commit();
} catch (\Exception $e) {
$connection->rollBack();
$this->logger->error($e->getMessage());
}
}
public function loadAll()
{
$files = $this->finder->in([
$this->ldrawLibraryContext->getAdapter()->getPathPrefix(),
])->path('parts/')->name('*.dat')->depth(1)->files();
$this->initProgressBar($files->count());
/** @var SplFileInfo $file */
foreach ($files as $file) {
$connection = $this->em->getConnection();
$connection->beginTransaction();
try {
$this->progressBar->setMessage($file->getFilename());
$this->loadModel($file->getRealPath());
$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)
{
$fileContext = $this->getFileContext($file);
// Return model from database if rewrite is not enabled
if (!$this->rewite && $model = $this->em->getRepository(Model::class)->findOneByNumber(basename($file, '.dat'))) {
return $model;
}
// Parse model file save data to $modelArray
try {
$content = file_get_contents($file);
$modelArray = $this->ldModelParser->parse($content);
} 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, $fileContext)) !== null) {
$parentModel = $this->loadModel($parentModelFile);
if ($parentModel) {
$alias = $this->em->getRepository(Alias::class)->getOrCreate($modelArray['id'], $parentModel);
$parentModel->addAlias($alias);
$this->em->getRepository(Model::class)->save($parentModel);
} else {
$this->logger->info('Model skipped. ', ['number' => $modelArray['id'], 'parent' => $modelArray['parent']]);
}
return $parentModel;
}
// Load model
$model = $this->em->getRepository(Model::class)->getOrCreate($modelArray['id']);
// Recursively load models of subparts
if (isset($modelArray['subparts'])) {
foreach ($modelArray['subparts'] as $subpartId => $count) {
// Try to find model of subpart
if (($subpartFile = $this->findSubmodelFile($subpartId, $fileContext)) !== null) {
$subModel = $this->loadModel($subpartFile);
if ($subModel) {
$subpart = $this->em->getRepository(Subpart::class)->getOrCreate($model, $subModel, $count);
$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->LDViewService->datToStl($file, $this->rewite)->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']);
$this->em->getRepository(Model::class)->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, $context)
{
// Replace "\" directory separator used inside ldraw model files with system directoru separator
$filename = str_replace('\\', DIRECTORY_SEPARATOR, strtolower($id).'.dat');
// Try to find model in current file's directory
if ($context->has($filename)) {
return $context->getAdapter()->getPathPrefix().$filename;
}
// Try to find model in current LDRAW\PARTS sub-directory
elseif($this->ldrawLibraryContext) {
if($this->ldrawLibraryContext->has('parts/'.$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));
return 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 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;
}
}