mirror of
https://github.com/ToxicCrack/PrintABrick.git
synced 2025-05-27 17:30:07 -07:00
Add Dat file parser
This commit is contained in:
parent
775af7c23b
commit
b373ee94f7
@ -1,8 +1,9 @@
|
||||
services:
|
||||
service.loader:
|
||||
abstract: true
|
||||
class: AppBundle\Loader\Loader
|
||||
arguments: ['@doctrine.orm.entity_manager']
|
||||
class: AppBundle\Service\Loader\BaseLoaderService
|
||||
calls:
|
||||
- [setArguments, ['@doctrine.orm.entity_manager', '@app.relation.mapper']]
|
||||
|
||||
service.ldview:
|
||||
class: AppBundle\Service\LDViewService
|
||||
@ -14,8 +15,11 @@ services:
|
||||
- [setArguments, ['@manager.rebrickable', '%rebrickable_url%']]
|
||||
parent: service.loader
|
||||
|
||||
loader.ldraw:
|
||||
class: AppBundle\Loader\LDrawLoader
|
||||
calls:
|
||||
- [setArguments, ['@service.ldview', '%ldraw_url%', '@service.ldraw']]
|
||||
util.dat.parser:
|
||||
class: AppBundle\Utils\DatParser
|
||||
arguments: ['@app.relation.mapper']
|
||||
|
||||
service.loader.ldraw:
|
||||
class: AppBundle\Service\Loader\LDrawLoaderService
|
||||
arguments: ['@service.ldview', '%ldraw_url%', '@manager.ldraw', '@util.dat.parser']
|
||||
parent: service.loader
|
||||
|
@ -1,414 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Loader;
|
||||
|
||||
use AppBundle\Entity\LDraw\Category;
|
||||
use AppBundle\Entity\LDraw\Keyword;
|
||||
use AppBundle\Entity\LDraw\Model;
|
||||
use AppBundle\Entity\LDraw\Part;
|
||||
use AppBundle\Entity\LDraw\Type;
|
||||
use AppBundle\Service\LDrawService;
|
||||
use AppBundle\Service\LDViewService;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\Filesystem;
|
||||
//use Symfony\Component\Asset\Exception\LogicException;
|
||||
use Symfony\Component\Asset\Exception\LogicException;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
||||
//TODO refactor
|
||||
class LDrawLoader extends Loader
|
||||
{
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
private $ldraw;
|
||||
|
||||
/**
|
||||
* @var string download URL with current LDraw library
|
||||
*/
|
||||
private $ldraw_url;
|
||||
|
||||
/**
|
||||
* @var LDViewService
|
||||
*/
|
||||
private $LDViewService;
|
||||
|
||||
/** @var LDrawService */
|
||||
private $ldrawService;
|
||||
|
||||
/**
|
||||
* @param array $ldraw_url
|
||||
*/
|
||||
public function setArguments(LDViewService $LDViewService, $ldraw_url, LDrawService $ldrawService)
|
||||
{
|
||||
$this->LDViewService = $LDViewService;
|
||||
$this->ldraw_url = $ldraw_url;
|
||||
$this->ldrawService = $ldrawService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download current LDraw library and extract it to system tmp directory.
|
||||
*
|
||||
* @return string Absolute path to temporary Ldraw library
|
||||
*/
|
||||
public function downloadLibrary()
|
||||
{
|
||||
$temp = $this->downloadFile($this->ldraw_url);
|
||||
|
||||
// Create unique temporary directory
|
||||
$temp_dir = tempnam(sys_get_temp_dir(), 'printabrick.');
|
||||
mkdir($temp_dir);
|
||||
|
||||
// Unzip downloaded library zip file to temporary directory
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($temp) != 'true') {
|
||||
throw new LogicException('Error :- Unable to open the Zip File');
|
||||
}
|
||||
$zip->extractTo($temp_dir);
|
||||
$zip->close();
|
||||
|
||||
// Unlink downloaded zip file
|
||||
unlink($temp);
|
||||
|
||||
return $temp_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $LDrawLibrary
|
||||
*/
|
||||
public function loadData($LDrawLibrary)
|
||||
{
|
||||
$adapter = new Local($LDrawLibrary);
|
||||
$this->ldraw = new Filesystem($adapter);
|
||||
|
||||
$this->loadParts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load stl model by calling LDViewSevice and create new Model.
|
||||
*
|
||||
* @param $file
|
||||
* @param $header
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return Model|null
|
||||
*/
|
||||
private function loadModel($file, $header)
|
||||
{
|
||||
if (($model = $this->em->getRepository(Model::class)->find($header['id'])) == null) {
|
||||
$model = new Model();
|
||||
$model->setNumber($header['id']);
|
||||
}
|
||||
|
||||
$model->setAuthor($header['author']);
|
||||
$model->setModified($header['modified']);
|
||||
|
||||
try {
|
||||
$stlFile = $this->LDViewService->datToStl($file, $this->ldraw)->getPath();
|
||||
$model->setFile($stlFile);
|
||||
} catch (\Exception $e) {
|
||||
throw $e; //TODO
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
// TODO refactor
|
||||
public function loadParts()
|
||||
{
|
||||
$partManager = $this->ldrawService->getPartManager();
|
||||
$relationManager = $this->ldrawService->getPartRelationManager();
|
||||
|
||||
$files = $this->ldraw->get('parts')->getContents();
|
||||
|
||||
$this->initProgressBar(count($files));
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file['type'] == 'file' && $file['extension'] == 'dat') {
|
||||
$header = $this->getPartHeader($file);
|
||||
|
||||
if ($this->isPartIncluded($header)) {
|
||||
$part = $partManager->create($header['id']);
|
||||
|
||||
$part->setName($header['name']);
|
||||
$part
|
||||
->setCategory($this->ldrawService->getCategoryManager()->create($header['category']))
|
||||
->setType($this->ldrawService->getTypeManager()->create($header['type']));
|
||||
|
||||
if (isset($header['keywords'])) {
|
||||
foreach ($header['keywords'] as $keyword) {
|
||||
$keyword = stripslashes(strtolower(trim($keyword)));
|
||||
$part->addKeyword($this->ldrawService->getKeywordManager()->create($keyword));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($header['subparts'])) {
|
||||
if ($header['type'] == 'Alias') {
|
||||
if (count($header['subparts']) == 1) {
|
||||
$relationType = 'Alias';
|
||||
} else {
|
||||
$relationType = 'Subpart';
|
||||
}
|
||||
} else {
|
||||
$relationType = 'Subpart';
|
||||
}
|
||||
|
||||
foreach ($header['subparts'] as $referenceId) {
|
||||
if ($referenceId != $this->getPrintedParentId($header['id'])) {
|
||||
if ($this->getModel($referenceId) && $this->isPartIncluded($this->getPartHeader($this->getModel($referenceId)->getMetadata()))) {
|
||||
$referencedPart = $this->ldrawService->getPartManager()->create($referenceId);
|
||||
|
||||
if ($relationType == 'Alias') {
|
||||
$parent = $referencedPart;
|
||||
$child = $part;
|
||||
} else {
|
||||
$parent = $part;
|
||||
$child = $referencedPart;
|
||||
}
|
||||
|
||||
$partRelation = $relationManager->create($parent, $child, $relationType);
|
||||
$partRelation->setCount($partRelation->getCount() + 1);
|
||||
|
||||
$relationManager->getRepository()->save($partRelation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($header['type'], ['Alias'])) {
|
||||
$part->setModel($this->loadModel($file, $header));
|
||||
}
|
||||
|
||||
// try {
|
||||
// $this->LDViewService->datToPng($file, $this->ldraw);
|
||||
// } catch (\Exception $e) {
|
||||
// dump($e->getMessage());
|
||||
// }
|
||||
|
||||
$partManager->getRepository()->save($part);
|
||||
}
|
||||
}
|
||||
$this->progressBar->advance();
|
||||
}
|
||||
$this->progressBar->finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if part file should be loaded into database.
|
||||
*
|
||||
* @param $header
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isPartIncluded($header)
|
||||
{
|
||||
// Do not include sticker parts and incomplete parts
|
||||
if (
|
||||
strpos($header['name'], 'Sticker') !== 0
|
||||
&& strpos($header['id'], 's') !== 0 && $header['type'] != 'Subpart'
|
||||
&& !$this->isStickerShortcutPart($header['id'])
|
||||
&& !($this->isPrintedPart($header['id']) && $this->getModel($this->getPrintedParentId($header['id'])))
|
||||
) {
|
||||
// If file is alias of another part determine if referenced file should be included
|
||||
if ($alias = $this->getObsoleteParentId($header['name'])) {
|
||||
if ($this->getModel($alias)) {
|
||||
return $this->isPartIncluded($this->getPartHeader($this->getModel($alias)->getMetadata()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isPrintedPart($id)
|
||||
{
|
||||
return preg_match('/(^.*)(p[0-9a-z][0-9a-z][0-9a-z]{0,1})$/', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get printed part parent id.
|
||||
*
|
||||
* part name in format:
|
||||
* nnnPxx, nnnnPxx, nnnnnPxx, nnnaPxx, nnnnaPxx (a = alpha, n= numeric, x = alphanumeric)
|
||||
*
|
||||
* http://www.ldraw.org/library/tracker/ref/numberfaq/
|
||||
*
|
||||
* @param $id
|
||||
*
|
||||
* @return string|null LDraw number of printed part parent
|
||||
*/
|
||||
private function getPrintedParentId($id)
|
||||
{
|
||||
if (preg_match('/(^.*)(p[0-9a-z][0-9a-z][0-9a-z]{0,1})$/', $id, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if part is shortcut part of stricker and part.
|
||||
*
|
||||
* part name in format:
|
||||
* nnnDnn, nnnnDnn, nnnnnDnn (a = alpha, n= numeric, x = alphanumeric)
|
||||
*
|
||||
* http://www.ldraw.org/library/tracker/ref/numberfaq/
|
||||
*
|
||||
* @param $id
|
||||
*
|
||||
* @return string|null LDraw number of printed part parent
|
||||
*/
|
||||
private function isStickerShortcutPart($id)
|
||||
{
|
||||
// some of files are in format nnnDaa
|
||||
return preg_match('/(^.*)(d[a-z0-9][a-z0-9])$/', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent of obsolete part kept for reference.
|
||||
*
|
||||
* part description in format:
|
||||
* ~Moved to {new_number}
|
||||
*
|
||||
* http://www.ldraw.org/article/398.html (Appendix II (02-Oct-06))
|
||||
*
|
||||
* @param $name
|
||||
*
|
||||
* @return string|null Filename of referenced part
|
||||
*/
|
||||
private function getObsoleteParentId($name)
|
||||
{
|
||||
if (preg_match('/^(~Moved to )(.*)$/', $name, $matches)) {
|
||||
return $matches[2];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getModel($id)
|
||||
{
|
||||
if ($this->ldraw->has('parts/'.$id.'.dat')) {
|
||||
return $this->ldraw->get('parts/'.$id.'.dat');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file reference from part line.
|
||||
*
|
||||
* Line type 1 is a sub-file reference. The generic format is:
|
||||
* 1 <colour> x y z a b c d e f g h i <file>
|
||||
*
|
||||
* LDraw.org Standards: File Format 1.0.2 (http://www.ldraw.org/article/218.html)
|
||||
*
|
||||
* @param $line
|
||||
*
|
||||
* @return string|null Filename of referenced part
|
||||
*/
|
||||
private function getAlias($line)
|
||||
{
|
||||
if (preg_match('/^1(.*) (.*)\.(dat|DAT)$/', $line, $matches)) {
|
||||
return $matches[2];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse LDraw .dat file header identifying model store data to array.
|
||||
*
|
||||
* [
|
||||
* 'id' => string
|
||||
* 'name' => string
|
||||
* 'category' => string
|
||||
* 'keywords' => []
|
||||
* 'author' => string
|
||||
* 'modified' => DateTime
|
||||
* 'type' => string
|
||||
* 'subparts' => []
|
||||
* ]
|
||||
*
|
||||
* LDraw.org Standards: Official Library Header Specification (http://www.ldraw.org/article/398.html)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getPartHeader($file)
|
||||
{
|
||||
$header = [];
|
||||
|
||||
$handle = $this->ldraw->readStream($file['path']);
|
||||
|
||||
if ($handle) {
|
||||
$firstLine = false;
|
||||
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = trim($line);
|
||||
|
||||
// Comments or META Commands
|
||||
if (strpos($line, '0 ') === 0) {
|
||||
$line = preg_replace('/^0 /', '', $line);
|
||||
|
||||
// 0 <CategoryName> <PartDescription>
|
||||
if (!$firstLine) {
|
||||
$array = explode(' ', ltrim(trim($line, 2), '=_~'));
|
||||
$header['category'] = isset($array[0]) ? $array[0] : '';
|
||||
$header['name'] = ltrim($line, '=_');
|
||||
|
||||
$firstLine = true;
|
||||
}
|
||||
// 0 !CATEGORY <CategoryName>
|
||||
elseif (strpos($line, '!CATEGORY ') === 0) {
|
||||
$header['category'] = trim(preg_replace('/^!CATEGORY /', '', $line));
|
||||
}
|
||||
// 0 !KEYWORDS <first keyword>, <second keyword>, ..., <last keyword>
|
||||
elseif (strpos($line, '!KEYWORDS ') === 0) {
|
||||
$header['keywords'] = explode(', ', preg_replace('/^!KEYWORDS /', '', $line));
|
||||
}
|
||||
// 0 Name: <Filename>.dat
|
||||
elseif (strpos($line, 'Name: ') === 0) {
|
||||
if (!isset($header['id'])) {
|
||||
$header['id'] = preg_replace('/(^Name: )(.*)(.dat)/', '$2', $line);
|
||||
}
|
||||
}
|
||||
// 0 Author: <Realname> [<Username>]
|
||||
elseif (strpos($line, 'Author: ') === 0) {
|
||||
$header['author'] = preg_replace('/^Author: /', '', $line);
|
||||
}
|
||||
// 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR
|
||||
elseif (strpos($line, '!LDRAW_ORG ') === 0) {
|
||||
$type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line);
|
||||
|
||||
$header['type'] = in_array($type, ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour']) ? 'Alias' : $type;
|
||||
|
||||
// Last modification date in format YYYY-RR
|
||||
$date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line);
|
||||
if (preg_match('/^[1-2][0-9]{3}-[0-9]{2}$/', $date)) {
|
||||
$header['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date.'-01 00:00:00');
|
||||
} else {
|
||||
$header['modified'] = null;
|
||||
}
|
||||
}
|
||||
} elseif (strpos($line, '1 ') === 0) {
|
||||
$header['subparts'][] = $this->getPrintedParentId($this->getAlias($line));
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($header['name'], '~Moved to') === 0) {
|
||||
$header['type'] = 'Alias';
|
||||
} elseif (strpos($header['name'], '~') === 0) {
|
||||
$header['type'] = 'Obsolete/Subpart';
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $header;
|
||||
}
|
||||
throw new LogicException('loadHeader error'); //TODO
|
||||
}
|
||||
}
|
@ -1,14 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Loader;
|
||||
namespace AppBundle\Service\Loader;
|
||||
|
||||
use AppBundle\Utils\RelationMapper;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
|
||||
use Symfony\Component\Asset\Exception\LogicException;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||
|
||||
abstract class Loader
|
||||
abstract class BaseLoaderService
|
||||
{
|
||||
/**
|
||||
* @var EntityManager
|
||||
@ -25,14 +27,19 @@ abstract class Loader
|
||||
*/
|
||||
protected $progressBar;
|
||||
|
||||
/** @var RelationMapper */
|
||||
protected $relationMapper;
|
||||
|
||||
/**
|
||||
* Loader constructor.
|
||||
*
|
||||
* @param EntityManager $em
|
||||
* @param Translator $translator
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
public function setArguments(EntityManager $em, $relationMapper)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->relationMapper = $relationMapper;
|
||||
$this->em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
}
|
||||
|
268
src/AppBundle/Service/Loader/LDrawLoaderService.php
Normal file
268
src/AppBundle/Service/Loader/LDrawLoaderService.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Service\Loader;
|
||||
|
||||
use AppBundle\Entity\LDraw\Category;
|
||||
use AppBundle\Entity\LDraw\Model;
|
||||
use AppBundle\Entity\LDraw\Type;
|
||||
use AppBundle\Manager\LDrawManager;
|
||||
use AppBundle\Service\LDrawService;
|
||||
use AppBundle\Service\LDViewService;
|
||||
use AppBundle\Utils\DatParser;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\File;
|
||||
use League\Flysystem\Filesystem;
|
||||
//use Symfony\Component\Asset\Exception\LogicException;
|
||||
use Symfony\Component\Asset\Exception\LogicException;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
||||
//TODO refactor
|
||||
class LDrawLoaderService extends BaseLoaderService
|
||||
{
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
private $ldraw;
|
||||
|
||||
/**
|
||||
* @var string download URL with current LDraw library
|
||||
*/
|
||||
private $ldraw_url;
|
||||
|
||||
/**
|
||||
* @var LDViewService
|
||||
*/
|
||||
private $LDViewService;
|
||||
|
||||
/** @var LDrawManager */
|
||||
private $ldrawService;
|
||||
|
||||
/** @var DatParser */
|
||||
private $datParser;
|
||||
|
||||
/**
|
||||
* @param array $ldraw_url
|
||||
*/
|
||||
public function __construct(LDViewService $LDViewService, $ldraw_url, LDrawManager $ldrawService, $datParser)
|
||||
{
|
||||
$this->LDViewService = $LDViewService;
|
||||
$this->ldraw_url = $ldraw_url;
|
||||
$this->ldrawService = $ldrawService;
|
||||
$this->datParser = $datParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download current LDraw library and extract it to system tmp directory.
|
||||
*
|
||||
* @return string Absolute path to temporary Ldraw library
|
||||
*/
|
||||
public function downloadLibrary()
|
||||
{
|
||||
$temp = $this->downloadFile($this->ldraw_url);
|
||||
|
||||
// Create unique temporary directory
|
||||
$temp_dir = tempnam(sys_get_temp_dir(), 'printabrick.');
|
||||
mkdir($temp_dir);
|
||||
|
||||
// Unzip downloaded library zip file to temporary directory
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($temp) != 'true') {
|
||||
throw new LogicException('Error :- Unable to open the Zip File');
|
||||
}
|
||||
$zip->extractTo($temp_dir);
|
||||
$zip->close();
|
||||
|
||||
// Unlink downloaded zip file
|
||||
unlink($temp);
|
||||
|
||||
return $temp_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $LDrawLibrary
|
||||
*/
|
||||
public function loadData($LDrawLibrary)
|
||||
{
|
||||
$adapter = new Local($LDrawLibrary);
|
||||
$this->ldraw = new Filesystem($adapter);
|
||||
|
||||
$this->LDViewService->setLdrawFilesystem($this->ldraw);
|
||||
|
||||
$this->loadParts();
|
||||
}
|
||||
|
||||
// TODO refactor
|
||||
|
||||
public function loadParts()
|
||||
{
|
||||
$files = $this->ldraw->get('parts')->getContents();
|
||||
|
||||
$this->initProgressBar(count($files));
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file['type'] == 'file' && $file['extension'] == 'dat') {
|
||||
$this->loadModel($file);
|
||||
}
|
||||
|
||||
$this->progressBar->advance();
|
||||
}
|
||||
$this->progressBar->finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Model entity into database.
|
||||
*
|
||||
* @param $file
|
||||
*
|
||||
* @return Model|null
|
||||
*/
|
||||
private function loadModel($file)
|
||||
{
|
||||
$modelManager = $this->ldrawService->getModelManager();
|
||||
$subpartManager = $this->ldrawService->getSubpartManager();
|
||||
$aliasManager = $this->ldrawService->getAliasManager();
|
||||
|
||||
$header = $this->datParser->parse($this->ldraw->get($file['path']));
|
||||
|
||||
if ($this->isModelIncluded($header)) {
|
||||
if (isset($header['parent']) && ($parent = $this->getModelParent($header['parent'])) && ($parentFile = $this->getModelFile($parent))) {
|
||||
$parentHeader = $this->datParser->parse($parentFile);
|
||||
|
||||
if ($this->isModelIncluded($parentHeader)) {
|
||||
$model = $modelManager->create($parentHeader['id']);
|
||||
$alias = $aliasManager->create($header['id'], $model);
|
||||
$aliasManager->getRepository()->save($alias);
|
||||
|
||||
return $model;
|
||||
}
|
||||
} else {
|
||||
$model = $modelManager->create($header['id']);
|
||||
$model->setName($header['name']);
|
||||
$model
|
||||
->setCategory($this->ldrawService->getCategoryManager()->create($header['category']))
|
||||
->setType($this->ldrawService->getTypeManager()->create($header['type']));
|
||||
|
||||
if (isset($header['keywords'])) {
|
||||
foreach ($header['keywords'] as $keyword) {
|
||||
$keyword = stripslashes(strtolower(trim($keyword)));
|
||||
$model->addKeyword($this->ldrawService->getKeywordManager()->create($keyword));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($header['subparts'])) {
|
||||
$model->setType($this->ldrawService->getTypeManager()->create('Shortcut'));
|
||||
|
||||
foreach ($header['subparts'] as $subpart) {
|
||||
$subpart = $this->getModelParent($subpart);
|
||||
|
||||
if ($subpartFile = $this->getModelFile($subpart)) {
|
||||
$subpartHeader = $this->datParser->parse($subpartFile);
|
||||
|
||||
if ($this->isModelIncluded($subpartHeader)) {
|
||||
$subpartModel = $modelManager->create($subpartHeader['id']);
|
||||
|
||||
$subpart = $subpartManager->create($model, $subpartModel);
|
||||
$subpartManager->getRepository()->save($subpart);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$model->setAuthor($header['author']);
|
||||
$model->setModified($header['modified']);
|
||||
$model->setPath($this->loadStlModel($file));
|
||||
|
||||
$modelManager->getRepository()->save($model);
|
||||
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find model file on ldraw filesystem.
|
||||
*
|
||||
* @param $id
|
||||
*
|
||||
* @return \League\Flysystem\Directory|File|\League\Flysystem\Handler|null
|
||||
*/
|
||||
private function getModelFile($id)
|
||||
{
|
||||
$path = 'parts/'.strtolower($id).'.dat';
|
||||
if ($this->ldraw->has($path)) {
|
||||
return $this->ldraw->get($path);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively load model parent id of model with $number id.
|
||||
*
|
||||
* @param string $number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getModelParent($number)
|
||||
{
|
||||
if (($file = $this->getModelFile($number)) == null) {
|
||||
return $number;
|
||||
}
|
||||
|
||||
$header = $this->datParser->parse($file);
|
||||
|
||||
do {
|
||||
$parent = isset($header['parent']) ? $header['parent'] : null;
|
||||
|
||||
if ($file = $this->getModelFile($parent)) {
|
||||
$header = $this->datParser->parse($file);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while ($parent);
|
||||
|
||||
return $header['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if model file should be loaded into database.
|
||||
*
|
||||
* @param $header
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isModelIncluded($header)
|
||||
{
|
||||
// Do not include sticker parts and incomplete parts
|
||||
if (
|
||||
strpos($header['id'], 's') !== 0
|
||||
&& $header['type'] != 'Subpart'
|
||||
&& $header['type'] != 'Sticker'
|
||||
&& !(($header['type'] == 'Printed') && $this->getModelFile($header['parent']))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load stl model by calling LDViewSevice and create new Model.
|
||||
*
|
||||
* @param $file
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return string path of stl file
|
||||
*/
|
||||
private function loadStlModel($file)
|
||||
{
|
||||
try {
|
||||
return $this->LDViewService->datToStl($file, $this->ldraw)->getPath();
|
||||
} catch (\Exception $e) {
|
||||
throw $e; //TODO
|
||||
}
|
||||
}
|
||||
}
|
215
src/AppBundle/Utils/DatParser.php
Normal file
215
src/AppBundle/Utils/DatParser.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace AppBundle\Utils;
|
||||
|
||||
use League\Flysystem\File;
|
||||
use Symfony\Component\Asset\Exception\LogicException;
|
||||
|
||||
class DatParser
|
||||
{
|
||||
/** @var RelationMapper */
|
||||
protected $relationMapper;
|
||||
|
||||
/**
|
||||
* DatParser constructor.
|
||||
*
|
||||
* @param RelationMapper $relationMapper
|
||||
*/
|
||||
public function __construct($relationMapper)
|
||||
{
|
||||
$this->relationMapper = $relationMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse LDraw .dat file header identifying model store data to array.
|
||||
*
|
||||
* [
|
||||
* 'id' => string
|
||||
* 'name' => string
|
||||
* 'category' => string
|
||||
* 'keywords' => []
|
||||
* 'author' => string
|
||||
* 'modified' => DateTime
|
||||
* 'type' => string
|
||||
* 'subparts' => []
|
||||
* ]
|
||||
*
|
||||
* LDraw.org Standards: Official Library Header Specification (http://www.ldraw.org/article/398.html)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse(File $file)
|
||||
{
|
||||
$header = [];
|
||||
|
||||
$handle = $file->readStream();
|
||||
|
||||
if ($handle) {
|
||||
$firstLine = false;
|
||||
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = trim($line);
|
||||
|
||||
// Comments or META Commands
|
||||
if (strpos($line, '0 ') === 0) {
|
||||
$line = preg_replace('/^0 /', '', $line);
|
||||
|
||||
// 0 <CategoryName> <PartDescription>
|
||||
if (!$firstLine) {
|
||||
$array = explode(' ', ltrim(trim($line, 2), '=_~'));
|
||||
$header['category'] = isset($array[0]) ? $array[0] : '';
|
||||
$header['name'] = preg_replace('/ {2,}/', ' ', ltrim($line, '=_'));
|
||||
|
||||
$firstLine = true;
|
||||
}
|
||||
// 0 !CATEGORY <CategoryName>
|
||||
elseif (strpos($line, '!CATEGORY ') === 0) {
|
||||
$header['category'] = trim(preg_replace('/^!CATEGORY /', '', $line));
|
||||
}
|
||||
// 0 !KEYWORDS <first keyword>, <second keyword>, ..., <last keyword>
|
||||
elseif (strpos($line, '!KEYWORDS ') === 0) {
|
||||
$header['keywords'] = explode(', ', preg_replace('/^!KEYWORDS /', '', $line));
|
||||
}
|
||||
// 0 Name: <Filename>.dat
|
||||
elseif (strpos($line, 'Name: ') === 0) {
|
||||
if (!isset($header['id'])) {
|
||||
$header['id'] = preg_replace('/(^Name: )(.*)(.dat)/', '$2', $line);
|
||||
}
|
||||
}
|
||||
// 0 Author: <Realname> [<Username>]
|
||||
elseif (strpos($line, 'Author: ') === 0) {
|
||||
$header['author'] = preg_replace('/^Author: /', '', $line);
|
||||
}
|
||||
// 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR
|
||||
elseif (strpos($line, '!LDRAW_ORG ') === 0) {
|
||||
$type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line);
|
||||
|
||||
$header['type'] = $type;
|
||||
|
||||
// Last modification date in format YYYY-RR
|
||||
$date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line);
|
||||
if (preg_match('/^[1-2][0-9]{3}-[0-9]{2}$/', $date)) {
|
||||
$header['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date.'-01 00:00:00');
|
||||
} else {
|
||||
$header['modified'] = null;
|
||||
}
|
||||
}
|
||||
} elseif (strpos($line, '1 ') === 0) {
|
||||
$header['subparts'][] = $this->getAlias($line);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isStickerShortcutPart($header['name'], $header['id'])) {
|
||||
$header['type'] = 'Sticker';
|
||||
} elseif (($parent = $this->relationMapper->find($header['id'], 'alias_model')) != $header['id']) {
|
||||
$header['type'] = 'Alias';
|
||||
$header['parent'] = $parent;
|
||||
} elseif (isset($header['subparts']) && count($header['subparts']) == 1 && in_array($header['type'], ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour'])) {
|
||||
$header['parent'] = $header['subparts'][0];
|
||||
$header['subparts'] = null;
|
||||
} elseif ($parent = $this->getPrintedParentId($header['id'])) {
|
||||
$header['type'] = 'Printed';
|
||||
$header['parent'] = $parent;
|
||||
} elseif ($parent = $this->getObsoleteParentId($header['name'])) {
|
||||
$header['type'] = 'Alias';
|
||||
$header['parent'] = $parent;
|
||||
} elseif (strpos($header['name'], '~') === 0 && $header['type'] != 'Alias') {
|
||||
$header['type'] = 'Obsolete/Subpart';
|
||||
}
|
||||
|
||||
$header['name'] = ltrim($header['name'], '~');
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
throw new LogicException('Error parsing DAT file'); //TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file reference from part line.
|
||||
*
|
||||
* Line type 1 is a sub-file reference. The generic format is:
|
||||
* 1 <colour> x y z a b c d e f g h i <file>
|
||||
*
|
||||
* LDraw.org Standards: File Format 1.0.2 (http://www.ldraw.org/article/218.html)
|
||||
*
|
||||
* @param $line
|
||||
*
|
||||
* @return string|null Filename of referenced part
|
||||
*/
|
||||
public function getAlias($line)
|
||||
{
|
||||
if (preg_match('/^1(.*) (.*)\.(dat|DAT)$/', $line, $matches)) {
|
||||
return $matches[2];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get printed part parent id.
|
||||
*
|
||||
* part name in format:
|
||||
* nnnPxx, nnnnPxx, nnnnnPxx, nnnaPxx, nnnnaPxx (a = alpha, n= numeric, x = alphanumeric)
|
||||
*
|
||||
* http://www.ldraw.org/library/tracker/ref/numberfaq/
|
||||
*
|
||||
* @param $id
|
||||
*
|
||||
* @return string|null LDraw number of printed part parent
|
||||
*/
|
||||
public function getPrintedParentId($id)
|
||||
{
|
||||
if (preg_match('/(^.*)(p[0-9a-z][0-9a-z][0-9a-z]{0,1})$/', $id, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if part is shortcut part of stricker and part.
|
||||
*
|
||||
* part name in format:
|
||||
* nnnDnn, nnnnDnn, nnnnnDnn (a = alpha, n= numeric, x = alphanumeric)
|
||||
*
|
||||
* http://www.ldraw.org/library/tracker/ref/numberfaq/
|
||||
*
|
||||
* @param $name
|
||||
* @param $number
|
||||
*
|
||||
* @return string|null LDraw number of printed part parent
|
||||
*/
|
||||
public function isStickerShortcutPart($name, $number)
|
||||
{
|
||||
if (strpos($name, 'Sticker') === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if in format nnnDaa == sticker
|
||||
return preg_match('/(^.*)(d[a-z0-9][a-z0-9])$/', $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent of obsolete part kept for reference.
|
||||
*
|
||||
* part description in format:
|
||||
* ~Moved to {new_number}
|
||||
*
|
||||
* http://www.ldraw.org/article/398.html (Appendix II (02-Oct-06))
|
||||
*
|
||||
* @param $name
|
||||
*
|
||||
* @return string|null Filename of referenced part
|
||||
*/
|
||||
public function getObsoleteParentId($name)
|
||||
{
|
||||
if (preg_match('/^(~Moved to )(.*)$/', $name, $matches)) {
|
||||
return $matches[2];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user