1
0
mirror of https://github.com/ToxicCrack/PrintABrick.git synced 2025-05-17 04:40:08 -07:00

Add Part_Relation class

Add Part_Relation to store references between parts
This commit is contained in:
David Hübner 2017-03-14 18:46:20 +01:00
parent e8d3c937d8
commit 15089022f1
4 changed files with 393 additions and 106 deletions

View File

@ -26,11 +26,12 @@ class LoadLDRawLibraryCommand extends ContainerAwareCommand
//TODO log errors //TODO log errors
try { try {
// TODO handle relative path to dir
if (($ldrawPath = $input->getArgument('ldraw_path')) == null) { if (($ldrawPath = $input->getArgument('ldraw_path')) == null) {
$ldrawPath = $ldrawLoader->downloadLibrary(); $ldrawPath = $ldrawLoader->downloadLibrary();
} }
$ldrawLoader->loadModels($ldrawPath); $ldrawLoader->loadData($ldrawPath);
} catch (\Exception $e) { } catch (\Exception $e) {
printf($e->getMessage()); printf($e->getMessage());
} }

View File

@ -4,12 +4,13 @@ namespace AppBundle\Entity\LDraw;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
/** /**
* Part. * Part.
* *
* @ORM\Entity * @ORM\Entity(repositoryClass="AppBundle\Repository\PartRepository")
* @ORM\Table(name="ldraw_part") * @ORM\Table(name="ldraw_part")
*/ */
class Part class Part
@ -43,18 +44,18 @@ class Part
private $category; private $category;
/** /**
* @var Part * @var Part_Relation
* *
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Part",cascade={"persist"}) * @ORM\OneToMany(targetEntity="AppBundle\Entity\LDraw\Part_Relation", mappedBy="parent")
*/ */
private $printOf; private $relationsTo;
/** /**
* @var Part * @var Part_Relation
* *
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Part",cascade={"persist"}) * @ORM\OneToMany(targetEntity="AppBundle\Entity\LDraw\Part_Relation", mappedBy="child")
*/ */
private $aliasOf; private $relationsFrom;
/** /**
* @var Model * @var Model
@ -73,6 +74,8 @@ class Part
public function __construct() public function __construct()
{ {
$this->keywords = new ArrayCollection(); $this->keywords = new ArrayCollection();
$this->relationsTo = new ArrayCollection();
$this->relationsFrom = new ArrayCollection();
} }
/** /**
@ -148,43 +151,35 @@ class Part
} }
/** /**
* @return Part * @return Part_Relation
*/ */
public function getPrintOf() public function getRelationsTo()
{ {
return $this->printOf; return $this->relationsTo;
} }
/** /**
* @param Part $printOf * @param Part_Relation $relationsTo
*
* @return Part
*/ */
public function setPrintOf($printOf) public function setRelationsTo($relationsTo)
{ {
$this->printOf = $printOf; $this->relationsTo = $relationsTo;
return $this;
} }
/** /**
* @return Part * @return Part_Relation
*/ */
public function getAliasOf() public function getRelationsFrom()
{ {
return $this->aliasOf; return $this->relationsFrom;
} }
/** /**
* @param Part $printOf * @param Part_Relation $relationsFrom
*
* @return Part
*/ */
public function setAliasOf($aliasOf) public function setRelationsFrom($relationsFrom)
{ {
$this->aliasOf = $aliasOf; $this->relationsFrom = $relationsFrom;
return $this;
} }
/** /**
@ -193,11 +188,12 @@ class Part
public function getModel() public function getModel()
{ {
if (!$this->model) { if (!$this->model) {
if ($this->printOf) { if ($this->getPrintOf()) {
return $this->printOf->getModel(); return $this->getPrintOf()->getModel();
} elseif ($this->aliasOf) { } elseif ($this->getAliasOf()) {
return $this->aliasOf->getModel(); return $this->getAliasOf()->getModel();
} }
return null; return null;
} }
@ -246,10 +242,80 @@ class Part
* *
* @return Part * @return Part
*/ */
public function removePart(Keyword $keyword) public function removeKeyword(Keyword $keyword)
{ {
$this->keywords->removeElement($keyword); $this->keywords->removeElement($keyword);
return $this; return $this;
} }
private function getRelationOf($type)
{
$criteria = new Criteria();
$criteria->where(Criteria::expr()->eq('type', $type));
$relations = $this->relationsFrom->matching($criteria);
$array = new ArrayCollection();
foreach ($relations as $relation) {
$array->add($relation->getParent());
}
return $array;
}
private function getRelations($type)
{
$criteria = new Criteria();
$criteria->where(Criteria::expr()->eq('type', $type));
$relations = $this->relationsTo->matching($criteria);
$array = new ArrayCollection();
foreach ($relations as $relation) {
$array->add($relation->getChild());
}
return $array;
}
public function getPrintOf()
{
$parents = $this->getRelationOf('Print');
if (count($parents) > 0) {
return $parents->first();
}
return null;
}
public function getPrints()
{
return $this->getRelations('Print');
}
public function getSubpartOf()
{
return $this->getRelationOf('Subpart');
}
public function getSubparts()
{
return $this->getRelations('Subpart');
}
public function getAliasOf()
{
$parents = $this->getRelationOf('Alias');
if (count($parents) > 0) {
return $parents->first();
}
return null;
}
public function getAliases()
{
return $this->getRelations('Alias');
}
} }

View File

@ -0,0 +1,109 @@
<?php
namespace AppBundle\Entity\LDraw;
use Doctrine\ORM\Mapping as ORM;
/**
* Part.
*
* @ORM\Entity
* @ORM\Table(name="ldraw_part_relation")
*/
class Part_Relation
{
/**
* @var Part
*
* @ORM\Id
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Part", inversedBy="relationsTo", cascade={"persist"})
*/
private $parent;
/**
* @var Part
*
* @ORM\Id
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Part", inversedBy="relationsFrom", cascade={"persist"} )
*/
private $child;
/**
* @var int
*
* @ORM\Column(type="integer")
*/
private $count;
/**
* @var string
*
* @ORM\Id
* @ORM\Column(type="string", length=255)
*/
private $type;
/**
* @return Part
*/
public function getParent()
{
return $this->parent;
}
/**
* @param Part $parent
*/
public function setParent($parent)
{
$this->parent = $parent;
}
/**
* @return Part
*/
public function getChild()
{
return $this->child;
}
/**
* @param Part $child
*/
public function setChild($child)
{
$this->child = $child;
}
/**
* @return int
*/
public function getCount()
{
return $this->count;
}
/**
* @param int $count
*/
public function setCount($count)
{
$this->count = $count;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @param string $type
*/
public function setType($type)
{
$this->type = $type;
}
}

View File

@ -6,14 +6,15 @@ use AppBundle\Entity\LDraw\Category;
use AppBundle\Entity\LDraw\Keyword; use AppBundle\Entity\LDraw\Keyword;
use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\LDraw\Model;
use AppBundle\Entity\LDraw\Part; use AppBundle\Entity\LDraw\Part;
use AppBundle\Entity\LDraw\Part_Relation;
use AppBundle\Entity\LDraw\Type; use AppBundle\Entity\LDraw\Type;
use AppBundle\Service\LDViewService; use AppBundle\Service\LDViewService;
use League\Flysystem\Adapter\Local; use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem; use League\Flysystem\Filesystem;
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\Finder\Finder;
//TODO refactor
class LDrawLoader extends Loader class LDrawLoader extends Loader
{ {
/** /**
@ -21,6 +22,9 @@ class LDrawLoader extends Loader
*/ */
private $ldraw; private $ldraw;
/**
* @var string download URL with current LDraw library
*/
private $ldraw_url; private $ldraw_url;
/** /**
@ -37,6 +41,11 @@ class LDrawLoader extends Loader
$this->ldraw_url = $ldraw_url; $this->ldraw_url = $ldraw_url;
} }
/**
* Download current LDraw library and extract it to system tmp directory.
*
* @return string Absolute path to temporary Ldraw library
*/
public function downloadLibrary() public function downloadLibrary()
{ {
$this->output->writeln('Downloading LDraw library form ldraw.org'); $this->output->writeln('Downloading LDraw library form ldraw.org');
@ -57,19 +66,54 @@ class LDrawLoader extends Loader
return $temp_dir; return $temp_dir;
} }
public function loadModels($LDrawLibrary) /**
* @param $LDrawLibrary
*/
public function loadData($LDrawLibrary)
{ {
$adapter = new Local($LDrawLibrary); $adapter = new Local($LDrawLibrary);
$this->ldraw = new Filesystem($adapter); $this->ldraw = new Filesystem($adapter);
// $files = $this->ldraw->get('parts')->getContents();
$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->setId($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()
{
$files = $this->ldraw->get('parts')->getContents(); $files = $this->ldraw->get('parts')->getContents();
$this->em->getConnection()->getConfiguration()->setSQLLogger(null); $this->em->getConnection()->getConfiguration()->setSQLLogger(null);
// $finder = new Finder();
// $files = $finder->files()->name('*.dat')->depth('== 0')->in(getcwd().DIRECTORY_SEPARATOR.$LDrawLibrary.DIRECTORY_SEPARATOR.'parts');
$progressBar = new ProgressBar($this->output, count($files)); $progressBar = new ProgressBar($this->output, count($files));
$progressBar->setFormat('very_verbose'); $progressBar->setFormat('very_verbose');
$progressBar->setMessage('Loading LDraw library models'); $progressBar->setMessage('Loading LDraw library models');
@ -110,40 +154,69 @@ class LDrawLoader extends Loader
} }
} }
if ($header['print_of_id']) { if ($printParentId = $this->getPrinetedParentId($header['id'])) {
if (($printParent = $this->em->getRepository(Part::class)->find($header['print_of_id'])) == null) { if (($printParent = $this->em->getRepository(Part::class)->find($printParentId)) == null) {
$printParent = new Part(); $printParent = new Part();
$printParent->setId($header['print_of_id']); $printParent->setId($printParentId);
if (!$this->ldraw->has('parts/'.$printParentId.'.dat')) {
$printParent->setModel($this->loadModel($file, $header));
}
} }
$part->setPrintOf($printParent);
if (($alias = $this->em->getRepository(Part_Relation::class)->find(['parent' => $printParent, 'child' => $part, 'type' => 'Print'])) == null) {
$alias = new Part_Relation();
$alias->setParent($printParent);
$alias->setChild($part);
$alias->setCount(0);
$alias->setType('Print');
}
$this->em->persist($alias);
} }
if ($header['alias_of_id']) { if (isset($header['subparts'])) {
if (($aliasParent = $this->em->getRepository(Part::class)->find($header['alias_of_id'])) == null) { $relationType = strpos($header['name'], '~Moved to ') === 0 ? 'Alias' : 'Subpart';
$aliasParent = new Part();
$aliasParent->setId($header['alias_of_id']); if ($header['type'] == 'Alias') {
if (count($header['subparts']) == 1) {
$relationType = 'Alias';
}
}
foreach ($header['subparts'] as $subId) {
if ($subId != $this->getPrinetedParentId($header['id'])) {
if ($this->ldraw->has('parts/'.$subId.'.dat') && $this->fileFilter($this->getPartHeader($this->ldraw->get('parts/'.$subId.'.dat')->getMetadata()))) {
if (($subPart = $this->em->getRepository(Part::class)->find($subId)) == null) {
$subPart = new Part();
$subPart->setId($subId);
$this->em->persist($subPart);
}
if (($alias = $this->em->getRepository(Part_Relation::class)->find(['parent' => $part, 'child' => $subPart, 'type' => $relationType])) == null) {
$alias = new Part_Relation();
$alias->setParent($part);
$alias->setChild($subPart);
$alias->setCount(0);
$alias->setType($relationType);
}
$alias->setCount($alias->getCount() + 1);
$this->em->persist($alias);
}
}
} }
$part->setAliasOf($aliasParent);
} }
if (!$header['print_of_id'] && !$header['alias_of_id']) { if (!in_array($header['type'], ['Print', 'Alias'])) {
if (($model = $this->em->getRepository(Model::class)->find($header['id'])) == null) { $part->setModel($this->loadModel($file, $header));
$model = new Model(); }
$model->setId($header['id']);
}
$model->setAuthor($header['author']); try {
$model->setModified($header['modified']); $this->LDViewService->datToPng($file, $this->ldraw);
} catch (\Exception $e) {
try { dump($e->getMessage());
$file = $this->LDViewService->datToStl($file, $this->ldraw)->getPath();
} catch (\Exception $e) {
dump($e);
}
$model->setFile($file);
$part->setModel($model);
} }
$this->em->persist($part); $this->em->persist($part);
@ -156,15 +229,26 @@ class LDrawLoader extends Loader
$progressBar->finish(); $progressBar->finish();
} }
/**
* Determine if part file should be loaded into database.
*
* @param $header
*
* @return bool
*/
private function fileFilter($header) private function fileFilter($header)
{ {
if (strpos($header['name'], 'Sticker') !== 0 && // Do not include sticker parts and incomplete parts
(strpos($header['name'], '~') !== 0 || strpos($header['name'], '~Moved to ') === 0) && if (strpos($header['name'], 'Sticker') !== 0 && strpos($header['id'], 's') !== 0 && $header['type'] != 'Subpart') {
$header['type'] !== 'Subpart') { // If file is alias of another part determine if referenced file should be included
if (strpos($header['name'], '~Moved to ') === 0) { if (strpos($header['name'], '~Moved to ') === 0) {
$filepath = str_replace('\\', DIRECTORY_SEPARATOR, 'parts/'.$header['alias_of_id'].'.dat'); // Get file path of referenced part file
$alias = 'parts/'.$this->getObsoleteParentId($header['name']).'.dat';
if ($this->ldraw->has($alias)) {
return $this->fileFilter($this->getPartHeader($this->ldraw->get($alias)->getMetadata()));
}
return $this->fileFilter($this->getPartHeader($this->ldraw->get($filepath)->getMetadata())); return false;
} }
return true; return true;
@ -173,11 +257,19 @@ class LDrawLoader extends Loader
return false; return false;
} }
/**
* 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 $filename
*/
private function getPrinetedParentId($filename) private function getPrinetedParentId($filename)
{ {
// nnnPxx, nnnnPxx, nnnnnPxx, nnnaPxx, nnnnaPxx
// where (a = alpha, n= numeric, x = alphanumeric)
if (preg_match('/(^.*)(p[0-9a-z][0-9a-z][0-9a-z]{0,1})$/', $filename, $matches)) { if (preg_match('/(^.*)(p[0-9a-z][0-9a-z][0-9a-z]{0,1})$/', $filename, $matches)) {
return $matches[1]; return $matches[1];
} }
@ -185,16 +277,19 @@ class LDrawLoader extends Loader
return null; return null;
} }
private function isShortcutPart($filename) /**
{ * Get parent of obsolete part kept for reference.
// nnnCnn, nnnnCnn, nnnnnCnn Shortcut assembly of part nnn, nnnn or nnnnn with other parts or formed version of flexible part nnn, nnnn or nnnnn. *
// nnnCnn-Fn, nnnnCnn-Fn, nnnnnCnn-Fn Positional variant of shortcut assembly of movable parts, comprising part nnn, nnnn or nnnnn with other parts. * part description in format:
// where (a = alpha, n= numeric, x = alphanumeric) * ~Moved to {new_number}
*
return preg_match('/(^.*)(c[0-9][0-9])(.*)/', $filename); * http://www.ldraw.org/article/398.html (Appendix II (02-Oct-06))
} *
* @param $name
private function getAliasParentId($name) *
* @return string|null Filename of referenced part
*/
private function getObsoleteParentId($name)
{ {
if (preg_match('/^(~Moved to )(.*)$/', $name, $matches)) { if (preg_match('/^(~Moved to )(.*)$/', $name, $matches)) {
return $matches[2]; return $matches[2];
@ -203,11 +298,21 @@ class LDrawLoader extends Loader
return null; 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) private function getAlias($line)
{ {
// 1 <colour> x y z a b c d e f g h i <file> if (preg_match('/^1(.*) (.*)\.(dat|DAT)$/', $line, $matches)) {
if (preg_match('/^1(.*) (.*)\.dat$/', $line, $matches)) {
return $matches[2]; return $matches[2];
} }
@ -215,14 +320,27 @@ class LDrawLoader extends Loader
} }
/** /**
* 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 * @return array
*/ */
private function getPartHeader($file) private function getPartHeader($file)
{ {
$header = []; $header = [];
// $handle = fopen($file->getRealPath(), 'r');
$handle = $this->ldraw->readStream($file['path']); $handle = $this->ldraw->readStream($file['path']);
if ($handle) { if ($handle) {
@ -237,9 +355,9 @@ class LDrawLoader extends Loader
// 0 <CategoryName> <PartDescription> // 0 <CategoryName> <PartDescription>
if (!$firstLine) { if (!$firstLine) {
$array = explode(' ', trim($line), 2); $array = explode(' ', ltrim(trim($line, 2), '=_~'));
$header['category'] = isset($array[0]) ? ltrim($array[0], '=_~') : ''; $header['category'] = isset($array[0]) ? $array[0] : '';
$header['name'] = $line; $header['name'] = ltrim($line, '=_');
$firstLine = true; $firstLine = true;
} }
@ -262,7 +380,8 @@ class LDrawLoader extends Loader
// 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR // 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR
elseif (strpos($line, '!LDRAW_ORG ') === 0) { elseif (strpos($line, '!LDRAW_ORG ') === 0) {
$type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line); $type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line);
$header['type'] = $type;
$header['type'] = in_array($type, ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour']) ? 'Alias' : $type;
// Last modification date in format YYYY-RR // Last modification date in format YYYY-RR
$date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line); $date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line);
@ -273,25 +392,17 @@ class LDrawLoader extends Loader
} }
} }
} elseif (strpos($line, '1 ') === 0) { } elseif (strpos($line, '1 ') === 0) {
if ($header['type'] == 'Part Alias' || $header['type'] == 'Shortcut Physical_Colour' || $header['type'] == 'Shortcut Alias') { $header['subparts'][] = $this->getAlias($line);
// "=" -> Alias name for other part kept for referece - do not include model -> LINK
// "_" -> Physical_color - do not include model -> LINK
$header['name'] = ltrim($header['name'], '=_');
$header['alias_of_id'] = $this->getAlias($line);
} elseif ($header['type'] == 'Shortcut') {
$header['subparts'][] = $this->getAlias($line);
}
} elseif ($line != '') { } elseif ($line != '') {
break; break;
} }
} }
if (isset($header['id'])) { if (strpos($header['name'], '~') === 0) {
$header['print_of_id'] = $this->getPrinetedParentId($header['id']); $header['name'] = ltrim($header['name'], '~');
} $header['type'] = 'Obsolete/Subpart';
} elseif ($printParentId = $this->getPrinetedParentId($header['id'])) {
if (isset($header['name']) && !isset($header['alias_of_id'])) { $header['type'] = 'Print';
$header['alias_of_id'] = $this->getAliasParentId($header['name']);
} }
fclose($handle); fclose($handle);