mirror of
https://github.com/ToxicCrack/PrintABrick.git
synced 2025-05-29 18:20:18 -07:00
Add Dat file parser
This commit is contained in:
parent
775af7c23b
commit
b373ee94f7
@ -1,8 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
service.loader:
|
service.loader:
|
||||||
abstract: true
|
abstract: true
|
||||||
class: AppBundle\Loader\Loader
|
class: AppBundle\Service\Loader\BaseLoaderService
|
||||||
arguments: ['@doctrine.orm.entity_manager']
|
calls:
|
||||||
|
- [setArguments, ['@doctrine.orm.entity_manager', '@app.relation.mapper']]
|
||||||
|
|
||||||
service.ldview:
|
service.ldview:
|
||||||
class: AppBundle\Service\LDViewService
|
class: AppBundle\Service\LDViewService
|
||||||
@ -14,8 +15,11 @@ services:
|
|||||||
- [setArguments, ['@manager.rebrickable', '%rebrickable_url%']]
|
- [setArguments, ['@manager.rebrickable', '%rebrickable_url%']]
|
||||||
parent: service.loader
|
parent: service.loader
|
||||||
|
|
||||||
loader.ldraw:
|
util.dat.parser:
|
||||||
class: AppBundle\Loader\LDrawLoader
|
class: AppBundle\Utils\DatParser
|
||||||
calls:
|
arguments: ['@app.relation.mapper']
|
||||||
- [setArguments, ['@service.ldview', '%ldraw_url%', '@service.ldraw']]
|
|
||||||
|
service.loader.ldraw:
|
||||||
|
class: AppBundle\Service\Loader\LDrawLoaderService
|
||||||
|
arguments: ['@service.ldview', '%ldraw_url%', '@manager.ldraw', '@util.dat.parser']
|
||||||
parent: service.loader
|
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
|
<?php
|
||||||
|
|
||||||
namespace AppBundle\Loader;
|
namespace AppBundle\Service\Loader;
|
||||||
|
|
||||||
|
use AppBundle\Utils\RelationMapper;
|
||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
|
||||||
use Symfony\Component\Asset\Exception\LogicException;
|
use Symfony\Component\Asset\Exception\LogicException;
|
||||||
use Symfony\Component\Console\Helper\ProgressBar;
|
use Symfony\Component\Console\Helper\ProgressBar;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Debug\Exception\ContextErrorException;
|
use Symfony\Component\Debug\Exception\ContextErrorException;
|
||||||
|
|
||||||
abstract class Loader
|
abstract class BaseLoaderService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var EntityManager
|
* @var EntityManager
|
||||||
@ -25,14 +27,19 @@ abstract class Loader
|
|||||||
*/
|
*/
|
||||||
protected $progressBar;
|
protected $progressBar;
|
||||||
|
|
||||||
|
/** @var RelationMapper */
|
||||||
|
protected $relationMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader constructor.
|
* Loader constructor.
|
||||||
*
|
*
|
||||||
* @param EntityManager $em
|
* @param EntityManager $em
|
||||||
|
* @param Translator $translator
|
||||||
*/
|
*/
|
||||||
public function __construct(EntityManager $em)
|
public function setArguments(EntityManager $em, $relationMapper)
|
||||||
{
|
{
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
|
$this->relationMapper = $relationMapper;
|
||||||
$this->em->getConnection()->getConfiguration()->setSQLLogger(null);
|
$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