1
0
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:
David Hübner 2017-04-05 17:07:45 +02:00
parent 775af7c23b
commit b373ee94f7
5 changed files with 503 additions and 423 deletions

View File

@ -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

View File

@ -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
}
}

View File

@ -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);
}

View 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
}
}
}

View 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;
}
}