diff --git a/.gitignore b/.gitignore index 3332cf1..33406e3 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,4 @@ install_plugin_cad.sh *# .#* \#*\# -Cube3 -sdf-generation -p.json \ No newline at end of file +out/ \ No newline at end of file diff --git a/asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx b/asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx index a08435f..d2a1584 100644 --- a/asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx +++ b/asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx @@ -6,7 +6,7 @@ import { HttpRepository, HttpMethod, HttpRoute } from '../../core/repository/htt export const pathAjaxTopologyScreen = '/topology/adjax/usecase/' export interface IAdjaxMatrix { - allPars: string[]; + allParts: string[]; firstDetail: string; matrix: StringMap; matrixError: StringMap | null; diff --git a/asp/helper/xmlformatter.py b/asp/helper/xmlformatter.py new file mode 100644 index 0000000..cf41b29 --- /dev/null +++ b/asp/helper/xmlformatter.py @@ -0,0 +1,877 @@ +""" +Format and compress XML documents +""" +import getopt +import re +import sys +import xml.parsers.expat + +__version__ = "0.2.4" + +DEFAULT_BLANKS = False +DEFAULT_COMPRESS = False +DEFAULT_SELFCLOSE = False +DEFAULT_CORRECT = True +DEFAULT_INDENT = 2 +DEFAULT_INDENT_CHAR = " " +DEFAULT_INLINE = True +DEFAULT_ENCODING_INPUT = None +DEFAULT_ENCODING_OUTPUT = None +DEFAULT_EOF_NEWLINE = False + + +class Formatter: + # Use internal encoding: + encoding_internal = None + + def __init__( + self, + indent=DEFAULT_INDENT, + preserve=[], + blanks=DEFAULT_BLANKS, + compress=DEFAULT_COMPRESS, + selfclose=DEFAULT_SELFCLOSE, + indent_char=DEFAULT_INDENT_CHAR, + encoding_input=DEFAULT_ENCODING_INPUT, + encoding_output=DEFAULT_ENCODING_OUTPUT, + inline=DEFAULT_INLINE, + correct=DEFAULT_CORRECT, + eof_newline=DEFAULT_EOF_NEWLINE, + ): + # Minify the XML document: + self.compress = compress + # Use self-closing tags + self.selfclose = selfclose + # Correct text nodes + self.correct = correct + # Decode the XML document: + self.encoding_input = self.enc_normalize(encoding_input) + # Encode ouput by: + self.encoding_output = self.enc_normalize(encoding_output) + # Insert indent = indent*level*indent_char: + self.indent = int(indent) + # Indent by char: + self.indent_char = indent_char + # Format inline objects: + self.inline = inline + # Don't compress this elements and their descendants: + self.preserve = preserve + # Preserve blanks lines (collapse multiple into one) + self.blanks = blanks + # Always add a newline character at EOF + self.eof_newline = eof_newline + + @property + def encoding_effective(self, enc=None): + if self.encoding_output: + return self.encoding_output + elif self.encoding_internal: + return self.encoding_internal + elif self.encoding_input: + return self.encoding_input + else: + return "UTF-8" + + def enc_normalize(self, string): + """ Format an Encoding identifier to upper case. """ + if isinstance(string, str): + return string.upper() + return None + + def enc_encode(self, strg): + """ Encode a formatted XML document in target""" + if sys.version_info > (3, 0): + return strg.encode(self.encoding_effective) # v3 + return strg.decode("utf-8").encode(self.encoding_effective) # v2 + + def enc_output(self, path, strg): + """ Output according to encoding """ + fh = sys.stdout + if strg is not None: + if path is not None: + open(path, "w+b").write(strg) + elif sys.version_info > (3, 0): + fh.buffer.write(strg) + else: + fh.write(strg) + + def format_string(self, xmldoc=""): + """ Format a XML document given by xmldoc """ + token_list = Formatter.TokenList(self) + token_list.parser.Parse(xmldoc) + return self.enc_encode(str(token_list)) + + def format_file(self, file): + """ Format a XML document given by path name """ + fh = open(file, "rb") + token_list = Formatter.TokenList(self) + token_list.parser.ParseFile(fh) + fh.close() + return self.enc_encode(str(token_list)) + + class TokenList: + # Being in a cdata section: + cdata_section = False + # Lock deletion of leading whitespace: + desc_mixed_level = None + # Lock indenting: + indent_level = None + # Reference the Formatter: + formatter = None + # Count levels: + level_counter = 0 + # Lock deletion of whitespaces: + preserve_level = None + + def __init__(self, formatter): + # Keep tokens in a list: + self._list = [] + self.formatter = formatter + self.parser = xml.parsers.expat.ParserCreate( + encoding=self.formatter.encoding_input + ) + self.parser.specified_attributes = 1 + self.parser.buffer_text = True + # Push tokens to buffer: + for pattern in [ + "XmlDecl%s", + "ElementDecl%s", + "AttlistDecl%s", + "EntityDecl%s", + "StartElement%s", + "EndElement%s", + "ProcessingInstruction%s", + "CharacterData%s", + "Comment%s", + "Default%s", + "StartDoctypeDecl%s", + "EndDoctypeDecl%s", + "StartCdataSection%s", + "EndCdataSection%s", + "NotationDecl%s", + ]: + setattr( + self.parser, pattern % "Handler", self.xml_handler(pattern % "") + ) + + def __iter__(self): + return iter(self._list) + + def __len__(self): + return len(self._list) + + def __getitem__(self, pos): + if 0 <= pos < len(self._list): + return self._list[pos] + else: + raise IndexError + + def __setitem__(self, pos, value): + if 0 <= pos < len(self._list): + self._list[pos] = value + else: + raise IndexError + + def __str__(self): + """ Returns the formatted XML document in UTF-8. """ + for step in ["configure", "pre_operate", "post_operate"]: + for tk in iter(self): + getattr(tk, step)() + result = "" + for tk in iter(self): + result += str(tk) + if self.formatter.eof_newline and not result.endswith("\n"): + result += "\n" + return result + + def append(self, tk): + """ Add token to tokenlist. """ + tk.pos = len(self._list) + self._list.append(tk) + + def level_increment(self): + """ Increment level counter. """ + self.level_counter += 1 + + def level_decrement(self): + """ Decrement level counter. """ + self.level_counter -= 1 + + def token_descendant_mixed(self, tk): + """ Mark descendants of mixed content. """ + if tk.name == "StartElement": + # Mark every descendant: + if tk.content_model in [2, 3] and self.desc_mixed_level is None: + self.desc_mixed_level = tk.level + return False + return self.desc_mixed_level is not None + elif tk.name == "EndElement": + # Stop marking every descendant: + if tk.level is self.desc_mixed_level: + self.desc_mixed_level = None + elif self.desc_mixed_level is not None: + return True + return False + elif self.desc_mixed_level is None: + return False + return self.desc_mixed_level >= tk.level - 1 + + def sequence(self, tk, scheme=None): + """Returns sublist of token list. + None: next to last + EndElement: first to previous""" + if scheme == "EndElement" or (scheme is None and tk.end): + return reversed(self._list[: tk.pos]) + return self._list[(tk.pos + 1) :] + + def token_indent(self, tk): + if self.formatter.inline: + return self.token_indent_inline(tk) + """ Indent outside of text of mixed content. """ + if tk.name == "StartElement": + # Block indenting for descendants of text and mixed content: + if tk.content_model in [2, 3] and self.indent_level is None: + self.indent_level = tk.level + elif self.indent_level is not None: + return False + return True + elif tk.name == "EndElement": + # Unblock indenting for descendants of text and mixed content: + if tk.level == self.indent_level: + self.indent_level = None + elif self.indent_level is None: + return True + return False + return self.indent_level is None + + def token_indent_inline(self, tk): + """ Indent every element content - no matter enclosed by text or mixed content. """ + for itk in iter(self.sequence(tk, "EndElement")): + if itk.level < tk.level and itk.name == "StartElement": + if itk.content_model == 1: + return True + return False + if ( + itk.level == tk.level + and tk.name == "EndElement" + and itk.name == "StartElement" + ): + if itk.content_model == 1: + return True + return False + return True + + def token_model(self, tk): + """Returns code for content model. + 0: empty + 1: element + 2: text + 3: mixed""" + eflag = tflag = 0 + for itk in iter(self.sequence(tk)): + # Element boundary found: + if itk.level <= tk.level: + break + # Direct child found: + elif (itk.level - 1) == tk.level: + if itk.start: + eflag = 1 + elif itk.not_empty: + tflag = 2 + return eflag + tflag + + def token_preserve(self, tk): + """Preseve eyery descendant of an preserved element. + 0: not locked + 1: just (un)locked + 2: locked""" + # Lock perserving for StartElements: + if tk.name == "StartElement": + if self.preserve_level is not None: + return 2 + if tk.arg[0] in self.formatter.preserve: + self.preserve_level = tk.level + return 1 + return 0 + # Unlock preserving for EndElements: + elif tk.name == "EndElement": + if ( + tk.arg[0] in self.formatter.preserve + and tk.level == self.preserve_level + ): + self.preserve_level = None + return 1 + elif self.preserve_level is None: + return 0 + return 2 + return self.preserve_level is not None + + def whitespace_append_trailing(self, tk): + """ Add a trailing whitespace to previous character data. """ + if self.formatter.correct and tk.leading and tk.not_empty: + self.whitespace_append(tk, "EndElement", "StartElement", True) + + def whitespace_append_leading(self, tk): + """ Add a leading whitespace to previous character data. """ + if self.formatter.correct and tk.trailing and tk.not_empty: + self.whitespace_append(tk) + + def whitespace_append( + self, tk, start="StartElement", stop="EndElement", direct=False + ): + """ Add a whitspace to token list. """ + for itk in self.sequence(tk, start): + if ( + itk.empty + or (itk.name == stop and itk.descendant_mixed is False) + or (itk.name == start and abs(tk - itk) == 1) + ): + break + elif itk.not_empty or (itk.name == start and itk.descendant_mixed): + self.insert_empty(itk, direct) + break + + def whitespace_delete_leading(self, tk): + """ Returns True, if no next token or all empty (up to next end element)""" + if ( + self.formatter.correct + and tk.leading + and not tk.preserve + and not tk.cdata_section + ): + for itk in self.sequence(tk, "EndElement"): + if itk.trailing: + return True + elif itk.name in ["EndElement", "CharacterData", "EndCdataSection"]: + return False + return True + return False + + def whitespace_delete_trailing(self, tk): + """Returns True, if no next token or all empty (up to next end element)""" + if ( + self.formatter.correct + and tk.trailing + and not tk.preserve + and not tk.cdata_section + ): + for itk in self.sequence(tk, "StartElement"): + if itk.end: + return True + elif ( + itk.name in ["StartElement", "StartCdataSection"] + or itk.not_empty + ): + return False + return True + return False + + def insert_empty(self, tk, before=True): + """ Insert an Empty Token into token list - before or after tk. """ + if not (0 < tk.pos < (len(self) - 1)): + return False + ptk = self[tk.pos - 1] + ntk = self.formatter.CharacterData(self, [" "]) + ntk.level = max(ptk.level, tk.level) + ntk.descendant_mixed = tk.descendant_mixed + ntk.preserve = ptk.preserve * tk.preserve + ntk.cdata_section = ptk.cdata_section or tk.cdata_section + if before: + self._list.insert(tk.pos + 1, ntk) + else: + self._list.insert(tk.pos, ntk) + for i in range((tk.pos - 1), len(self._list)): + self._list[i].pos = i + + def xml_handler(self, key): + """ Returns lambda function which adds token to token list""" + return lambda *arg: self.append(getattr(self.formatter, key)(self, arg)) + + class Token(object): + def __init__(self, tklist, arg): + # Reference Token List: + self.list = tklist + # Token datas: + self.arg = list(arg) + # Token is placed in an CDATA section: + self.cdata_section = False + # Token has content model: + self.content_model = None + # Remove trailing wihtespaces: + self.delete_trailing = False + # Remove leading whitespaces: + self.delete_leading = False + # Token is descendant of text or mixed content element: + self.descendant_mixed = False + # Reference to formatter: + self.formatter = tklist.formatter + # Insert indenting white spaces: + self.indent = False + # N-th generation of roots descendants: + self.level = self.list.level_counter + # Token class: + self.name = self.__class__.__name__ + # Preserve white spaces within enclosed tokens: + self.preserve = False + # Position in token list: + self.pos = None + + def __sub__(self, other): + return self.pos - other.pos + + def __unicode__(self): + return "" + + # Workaround, see http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/: + if sys.version_info > (3, 0): + __str__ = lambda x: x.__unicode__() + else: + __str__ = lambda x: unicode(x).encode("utf-8") + + @property + def end(self): + return self.name == "EndElement" + + @property + def empty(self): + return self.name == "CharacterData" and re.match( + r"^[\t\s\n]*$", self.arg[0] + ) + + @property + def leading(self): + return self.name == "CharacterData" and re.search( + r"^[\t\s\n]+", self.arg[0] + ) + + @property + def not_empty(self): + return ( + self.name == "CharacterData" + and not self.cdata_section + and not re.match(r"^[\t\s\n]+$", self.arg[0]) + ) + + @property + def trailing(self): + return self.name == "CharacterData" and re.search( + r"[\t\s\n]+$", self.arg[0] + ) + + @property + def start(self): + return self.name == "StartElement" + + @property + def correct(self): + return self.formatter.correct + + def attribute(self, key, value): + if key and value: + return ' %s="%s"' % (key, value) + elif key: + return ' %s=""' % (key) + return "" + + def indent_insert(self): + """ Indent token. """ + # Child of root and no empty node + if ( + self.level > 0 and not (self.end and self.list[self.pos - 1].start) + ) or ( # not empty node: + self.end and not self.list[self.pos - 1].start + ): + return self.indent_create(self.level) + return "" + + def indent_create(self, times=1): + """ Returns indent string. """ + if not self.formatter.compress and self.formatter.indent: + return "\n%s" % ( + (times * self.formatter.indent) * self.formatter.indent_char + ) + return "" + + def identifier(self, systemid, publicid): + # TODO add base parameter: + if publicid and systemid: + return ' PUBLIC "%s" "%s"' % (publicid, systemid) + elif publicid: + return ' PUBLIC "%s"' % publicid + elif systemid: + return ' SYSTEM "%s"' % systemid + return "" + + def configure(self): + """ Set token properties. """ + self.descendant_mixed = self.list.token_descendant_mixed(self) + self.preserve = self.list.token_preserve(self) + self.cdata_section = self.list.cdata_section + + def pre_operate(self): + pass + + def post_operate(self): + pass + + class AttlistDecl(Token): + def __unicode__(self): + str = self.indent_create() + str += "" + return str + + class CharacterData(Token): + def __unicode__(self): + str = self.arg[0] + if not self.preserve and not self.cdata_section: + # remove empty tokens always in element content! + if self.empty and not self.descendant_mixed: + if self.formatter.blanks and not self.formatter.compress and re.match(r"\s*\n\s*\n\s*", str): + str = "\n" + else: + str = "" + else: + if self.correct: + str = re.sub(r"\r\n", "\n", str) + str = re.sub(r"\r|\n|\t", " ", str) + str = re.sub(r"\s+", " ", str) + if self.delete_leading: + str = re.sub(r"^\s", "", str) + if self.delete_trailing: + str = re.sub(r"\s$", "", str) + if not self.cdata_section: + str = re.sub(r"&", "&", str) + str = re.sub(r"<", "<", str) + return str + + def pre_operate(self): + self.list.whitespace_append_trailing(self) + self.list.whitespace_append_leading(self) + + def post_operate(self): + self.delete_leading = self.list.whitespace_delete_leading(self) + self.delete_trailing = self.list.whitespace_delete_trailing(self) + + class Comment(Token): + def __unicode__(self): + str = "" + if self.preserve in [0, 1] and self.indent: + str += self.indent_insert() + str += "" % re.sub( + r"^[\r\n]+$", "\n", re.sub(r"^[\r\n]+", "\n", self.arg[0]) + ) + return str + + def configure(self): + super(Formatter.Comment, self).configure() + self.indent = self.list.token_indent(self) + + class Default(Token): + pass + + class EndCdataSection(Token): + def __unicode__(self): + return "]]>" + + def configure(self): + self.list.cdata_section = False + + class ElementDecl(Token): + def __unicode__(self): + str = self.indent_create() + str += "" % (self.arg[0], self.evaluate_model(self.arg[1])) + return str + + def evaluate_model(self, model, modelStr="", concatStr=""): + childSeq = [] + mixed = model[0] == xml.parsers.expat.model.XML_CTYPE_MIXED + hasChilds = len(model[3]) or mixed + if model[0] == xml.parsers.expat.model.XML_CTYPE_EMPTY: # 1 + modelStr += " EMPTY" + elif model[0] == xml.parsers.expat.model.XML_CTYPE_ANY: # 2 + modelStr += " ANY" + elif model[0] == xml.parsers.expat.model.XML_CTYPE_NAME: # 4 + modelStr = "%s" % model[2] # new start + elif model[0] in ( + xml.parsers.expat.model.XML_CTYPE_CHOICE, + xml.parsers.expat.model.XML_CTYPE_MIXED, + ): # 5 + concatStr = "|" + elif model[0] == xml.parsers.expat.model.XML_CTYPE_SEQ: # 6 + concatStr = "," + if hasChilds: + modelStr += " (" + if mixed: + childSeq.append("#PCDATA") + for child in model[3]: + childSeq.append(self.evaluate_model(child)) + modelStr += concatStr.join(childSeq) + if hasChilds: + modelStr += ")" + modelStr += { + xml.parsers.expat.model.XML_CQUANT_NONE: "", + xml.parsers.expat.model.XML_CQUANT_OPT: "?", + xml.parsers.expat.model.XML_CQUANT_PLUS: "+", + xml.parsers.expat.model.XML_CQUANT_REP: "*", + }[model[1]] + return modelStr + + class EndDoctypeDecl(Token): + def __unicode__(self): + str = "" + if self.list[self.pos - 1].name != "StartDoctypeDecl": + str += self.indent_create(0) + str += "]" + str += ">" + str += self.indent_create(0) + return str + + class EndElement(Token): + def __init__(self, list, arg): + list.level_decrement() + super(Formatter.EndElement, self).__init__(list, arg) + + def __unicode__(self): + str = "" + # Don't close empty nodes on compression mode: + if ( + not (self.formatter.compress or self.formatter.selfclose) + or self.list[self.pos - 1].name != "StartElement" + ): + if self.preserve in [0] and self.indent: + str += self.indent_insert() + str += "" % self.arg[0] + return str + + def configure(self): + self.descendant_mixed = self.list.token_descendant_mixed(self) + self.preserve = self.list.token_preserve(self) + self.indent = self.list.token_indent(self) + + class EntityDecl(Token): + def __unicode__(self): + str = self.indent_create() + str += "" + return str + + class NotationDecl(Token): + def __unicode__(self): + str = self.indent_create() + str += "" % ( + self.arg[0], + self.identifier(self.arg[2], self.arg[3]), + ) + return str + + class ProcessingInstruction(Token): + def __unicode__(self): + str = "" + if self.preserve in [0, 1] and self.indent: + str += self.indent_insert() + str += "" % (self.arg[0], self.arg[1]) + return str + + def configure(self): + super(Formatter.ProcessingInstruction, self).configure() + self.indent = self.list.token_indent(self) + + class StartCdataSection(Token): + def __unicode__(self): + return "" + else: + str += ">" + return str + + def configure(self): + self.content_model = self.list.token_model(self) + self.descendant_mixed = self.list.token_descendant_mixed(self) + self.preserve = self.list.token_preserve(self) + self.indent = self.list.token_indent(self) + + class XmlDecl(Token): + def __init__(self, list, arg): + super(Formatter.XmlDecl, self).__init__(list, arg) + if len(self.arg) > 1: + self.formatter.encoding_internal = self.arg[1] + + def __unicode__(self): + str = " -1: + str += self.attribute("standalone", "yes") + str += "?>\n" + return str + + +def cli_usage(msg=""): + """ Output usage for command line tool. """ + sys.stderr.write(msg + "\n") + sys.stderr.write( + 'Usage: xmlformat [--preserve "pre,literal"] [--blanks]\ + [--compress] [--selfclose] [--indent num] [--indent-char char]\ + [--outfile file] [--encoding enc] [--outencoding enc]\ + [--disable-inlineformatting] [--overwrite] [--disable-correction]\ + [--eof-newline]\ + [--help] <--infile file | file | - >\n' + ) + sys.exit(2) + + +def cli(): + """ Launch xmlformatter from command line. """ + res = None + indent = DEFAULT_INDENT + indent_char = DEFAULT_INDENT_CHAR + outfile = None + overwrite = False + preserve = [] + blanks = False + compress = DEFAULT_COMPRESS + selfclose = DEFAULT_SELFCLOSE + infile = None + encoding = DEFAULT_ENCODING_INPUT + outencoding = DEFAULT_ENCODING_OUTPUT + inline = DEFAULT_INLINE + correct = DEFAULT_CORRECT + eof_newline = DEFAULT_EOF_NEWLINE + try: + opts, args = getopt.getopt( + sys.argv[1:], + "", + [ + "compress", + "selfclose", + "disable-correction", + "disable-inlineformatting", + "encoding=", + "help", + "infile=", + "indent=", + "indent-char=", + "outfile=", + "outencoding=", + "overwrite", + "preserve=", + "blanks", + "eof-newline" + ], + ) + except getopt.GetoptError as err: + cli_usage(str(err)) + for key, value in opts: + if key in ["--indent"]: + indent = value + elif key in ["--preserve"]: + preserve = value.replace(",", " ").split() + elif key in ["--blanks"]: + blanks = True + elif key in ["--help"]: + cli_usage() + elif key in ["--compress"]: + compress = True + elif key in ["--selfclose"]: + selfclose = True + elif key in ["--outfile"]: + outfile = value + elif key in ["--infile"]: + infile = value + elif key in ["--encoding"]: + encoding = value + elif key in ["--outencoding"]: + outencoding = value + elif key in ["--indent-char"]: + indent_char = value + elif key in ["--disable-inlineformatting"]: + inline = False + elif key in ["--disable-correction"]: + correct = False + elif key in ["--overwrite"]: + overwrite = True + elif key in ["--eof-newline"]: + eof_newline = True + try: + formatter = Formatter( + indent=indent, + preserve=preserve, + blanks=blanks, + compress=compress, + selfclose=selfclose, + encoding_input=encoding, + encoding_output=outencoding, + indent_char=indent_char, + inline=inline, + correct=correct, + eof_newline=eof_newline, + ) + input_file = None + if infile: + input_file = infile + res = formatter.format_file(input_file) + elif len(args) > 0: + if args[0] == "-": + res = formatter.format_string("".join(sys.stdin.readlines())) + else: + input_file = args[0] + res = formatter.format_file(input_file) + + except xml.parsers.expat.ExpatError as err: + cli_usage("XML error: %s" % err) + except IOError as err: + cli_usage("IO error: %s" % err) + except: + cli_usage("Unkonwn error") + + if overwrite: + formatter.enc_output(input_file, res) + else: + formatter.enc_output(outfile, res) \ No newline at end of file diff --git a/asp/main.py b/asp/main.py index dba64a5..17783c9 100644 --- a/asp/main.py +++ b/asp/main.py @@ -7,7 +7,6 @@ from src.usecases.sdf_sub_assembly_usecase import SdfSubAssemblyUseCase import os - if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--generationFolder', help='FreeCad generation folder') @@ -27,8 +26,8 @@ if __name__ == "__main__": for el in geometryFiles: geometryModels.append(GeometryModel.from_dict( FS.readJSON(args.generationFolder + '/assets/' + el))) - if os.path.exists(outPath + 'sdf-generation/'): - shutil.rmtree(path=outPath + 'sdf-generation/') + # if os.path.exists(outPath + 'sdf-generation/'): + # shutil.rmtree(path=outPath + 'sdf-generation/') if (args.format == 'sdf'): SdfSubAssemblyUseCase().call( diff --git a/asp/mocks/urdf/model.urdf b/asp/mocks/urdf/model.urdf index 56e19d8..74aec10 100644 --- a/asp/mocks/urdf/model.urdf +++ b/asp/mocks/urdf/model.urdf @@ -15,7 +15,7 @@ - + @@ -23,14 +23,14 @@ - + - 0.2 - 0.1 - 1 0 0 + 0.2 + 0.1 + 1 0 0 diff --git a/asp/src/model/sdf_geometry.py b/asp/src/model/sdf_geometry.py index 08b7e44..3fdaa8b 100644 --- a/asp/src/model/sdf_geometry.py +++ b/asp/src/model/sdf_geometry.py @@ -30,6 +30,9 @@ def to_class(c, x): return x.to_dict() +DELIMITER_SCALE = 10000 + + class GeometryModel: def __init__(self, name, ixx, ixy, ixz, iyy, izz, massSDF, posX, posY, posZ, eulerX, eulerY, eulerZ, iyz, stl, link, friction, centerMassX, centerMassY, centerMassZ): self.name = name diff --git a/asp/src/usecases/formatter_usecase.py b/asp/src/usecases/formatter_usecase.py index 9efa2e8..f1a9d97 100644 --- a/asp/src/usecases/formatter_usecase.py +++ b/asp/src/usecases/formatter_usecase.py @@ -1,10 +1,10 @@ +from helper.xmlformatter import Formatter from src.model.enum import Enum from helper.fs import FS -import xmlformatter - + class FormatterUseCase: def call(outPath: str, format: str): - formatter = xmlformatter( + formatter = Formatter( indent="1", indent_char="\t", encoding_output="ISO-8859-1", preserve=["literal"]) files = FS.readFilesTypeFolder( diff --git a/asp/src/usecases/urdf_sub_assembly_usecase.py b/asp/src/usecases/urdf_sub_assembly_usecase.py index f035e5f..4d652f7 100644 --- a/asp/src/usecases/urdf_sub_assembly_usecase.py +++ b/asp/src/usecases/urdf_sub_assembly_usecase.py @@ -1,16 +1,11 @@ -from typing import Optional from helper.fs import FS from src.model.enum import Enum from src.model.asm import Assembly from src.model.sdf_geometry import GeometryModel -from helper.fs import filterModels, listGetFirstValue import json import re - - - URDF_FILE_FORMAT = '.urdf' URDF_GENERATOR_FILE = 'urdf-generation' + '.json' @@ -19,6 +14,6 @@ class UrdfSubAssemblyUseCase(Assembly): dirPath = generationFolder + Enum.folderPath asm = {} for el in geometryModels: - asm[el.name] = el.toUrdf() + asm[el.name] = el.toUrdf() FS.writeFile(data=json.dumps(asm,indent=4), fileName=URDF_GENERATOR_FILE, filePath=dirPath) diff --git a/cad_generation/env.json b/cad_generation/env.json index 3945e7a..f9633a4 100644 --- a/cad_generation/env.json +++ b/cad_generation/env.json @@ -1,6 +1,6 @@ { - "doc": "/home/idontsudo/t/framework/asp-review-app/server/public/cubes/cubes.FCStd", - "out": "/home/idontsudo/t/framework/cad_generation", + "doc": "/home/idontsudo/framework/asp/out/disk_and_axis_n.FCStd", + "out": "/home/idontsudo/framework/asp/out", "resultURL": "http://localhost:3002/assembly/save/out", "projectId": "cubes" } \ No newline at end of file diff --git a/cad_generation/scenarios/robossembler_freecad_export_scenario.py b/cad_generation/scenarios/robossembler_freecad_export_scenario.py index 717a127..c5b74da 100644 --- a/cad_generation/scenarios/robossembler_freecad_export_scenario.py +++ b/cad_generation/scenarios/robossembler_freecad_export_scenario.py @@ -36,9 +36,9 @@ class RobossemblerFreeCadExportScenario: self.geometry(directory) ExportAssemblyThemAllUseCase().call(directoryExport) - shutil.make_archive(directory, 'zip', directory) + # shutil.make_archive(directory, 'zip', directory) - shutil.rmtree(directory) + # shutil.rmtree(directory) return True def geometry(self, outPutsPath: str): diff --git a/cad_generation/usecases/get_sdf_geometry_usecase.py b/cad_generation/usecases/get_sdf_geometry_usecase.py index 4a4ccb4..6ea2931 100644 --- a/cad_generation/usecases/get_sdf_geometry_usecase.py +++ b/cad_generation/usecases/get_sdf_geometry_usecase.py @@ -1,38 +1,41 @@ import FreeCAD as App from model.sdf_geometry_model import SdfGeometryModel - + from helper.is_solid import is_object_solid class SdfGeometryUseCase: - ShapePropertyCheck = ['Mass','MatrixOfInertia','Placement', ] + ShapePropertyCheck = ['Mass', 'MatrixOfInertia', 'Placement', ] PartPropertyCheck = ['Shape'] - def call(self, stlPaths:dict) -> list[SdfGeometryModel]: + + def call(self, stlPaths: dict) -> list[SdfGeometryModel]: materialSolid = {} for el in App.ActiveDocument.Objects: - if str(el) == '': + if str(el) == '': friction = el.Material.get('SlidingFriction') for i in el.References: materialSolid[i[0].Label] = friction geometry = [] try: for el in App.ActiveDocument.Objects: + if is_object_solid(el): mass = el.Shape.Mass inertia = el.Shape.MatrixOfInertia pos = el.Shape.Placement inertia = el.Shape.MatrixOfInertia name = el.Label - ixx = str(inertia.A11 / 1000000) - ixy = str(inertia.A12 / 1000000) - ixz = str(inertia.A13 / 1000000) - iyy = str(inertia.A22 / 1000000) - iyz = str(inertia.A23 / 1000000) - izz = str(inertia.A33 / 1000000) - massSDF = str(mass / 1000000) - posX = str(pos.Base[0] / 1000000) - posY = str(pos.Base[1] / 1000000) - posZ = str(pos.Base[2] / 1000000) + delimiter = 1000000 + ixx = str(inertia.A11 / delimiter) + ixy = str(inertia.A12 / delimiter) + ixz = str(inertia.A13 / delimiter) + iyy = str(inertia.A22 / delimiter) + iyz = str(inertia.A23 / delimiter) + izz = str(inertia.A33 / delimiter) + massSDF = str(mass / delimiter) + posX = str(pos.Base[0] / delimiter) + posY = str(pos.Base[1] / delimiter) + posZ = str(pos.Base[2] / delimiter) eulerX = str(pos.Rotation.toEuler()[0]) eulerY = str(pos.Rotation.toEuler()[1]) eulerZ = str(pos.Rotation.toEuler()[2]) @@ -42,10 +45,10 @@ class SdfGeometryUseCase: geometry.append( SdfGeometryModel( stl=stlPaths.get(el.Label), - name=name, + name=name, ixx=ixx, - ixz=ixz, - ixy=ixy, + ixz=ixz, + ixy=ixy, iyy=iyy, iyz=iyz, izz=izz, @@ -53,8 +56,8 @@ class SdfGeometryUseCase: posX=posX, posY=posY, posZ=posZ, - eulerX=eulerX, - eulerY=eulerY, + eulerX=eulerX, + eulerY=eulerY, eulerZ=eulerZ, friction=materialSolid.get(el.Label) or '', centerMassX=centerMassX, @@ -64,10 +67,4 @@ class SdfGeometryUseCase: ) except Exception as e: print(e) - return geometry - - - - - diff --git a/cad_stability_check/.gitignore b/cad_stability_check/.gitignore index 2b2f40c..e482277 100644 --- a/cad_stability_check/.gitignore +++ b/cad_stability_check/.gitignore @@ -92,4 +92,5 @@ ENV/ # Rope project settings .ropeproject -env.json \ No newline at end of file +env.json +out/ \ No newline at end of file diff --git a/cad_stability_check/main.py b/cad_stability_check/main.py index d44593f..d0f91a2 100644 --- a/cad_stability_check/main.py +++ b/cad_stability_check/main.py @@ -1,15 +1,266 @@ # Алгоритм генерации графа с помощью оценки стабильности подсборок в физическом движке PyBullet +from typing import Any, TypeVar, Type, cast import FreeCAD as App import json -import re -from pyquaternion import Quaternion +import importOBJ +import FreeCAD as App +import Draft +import os +import Part +import numpy as np +from typing import TypeAlias +import FreeCADGui as Gui + +ISequencesUnion: TypeAlias = dict[str:dict[str:list[str]]] -def importObjAtPath(path: str): - import importOBJ +def importObjAtPath(path: str, inc: int): importOBJ.insert(u"" + path, App.ActiveDocument.Label) - pass + mesh = App.ActiveDocument.Objects[inc] + shape = Part.Shape() + shape.makeShapeFromMesh(mesh.Mesh.Topology, 0.05) + solid = Part.makeSolid(shape) + Part.show(solid) + App.ActiveDocument.Objects[inc + + 1].Label = App.ActiveDocument.Objects[inc].Name + App.ActiveDocument.removeObject(App.ActiveDocument.Objects[inc].Name) + return App.ActiveDocument.Objects[inc] + + +T = TypeVar("T") + + +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + + +def to_float(x: Any) -> float: + assert isinstance(x, float) + return x + + +def from_str(x: Any) -> str: + assert isinstance(x, str) + return x + + +def to_class(c: Type[T], x: Any) -> dict: + assert isinstance(x, c) + return cast(Any, x).to_dict() + + +def euler_to_quaternion(yaw, pitch, roll): + + qx = np.sin(roll/2) * np.cos(pitch/2) * np.cos(yaw/2) - \ + np.cos(roll/2) * np.sin(pitch/2) * np.sin(yaw/2) + qy = np.cos(roll/2) * np.sin(pitch/2) * np.cos(yaw/2) + \ + np.sin(roll/2) * np.cos(pitch/2) * np.sin(yaw/2) + qz = np.cos(roll/2) * np.cos(pitch/2) * np.sin(yaw/2) - \ + np.sin(roll/2) * np.sin(pitch/2) * np.cos(yaw/2) + qw = np.cos(roll/2) * np.cos(pitch/2) * np.cos(yaw/2) + \ + np.sin(roll/2) * np.sin(pitch/2) * np.sin(yaw/2) + + return [qx, qy, qz, qw] + + +class Coords: + x: float + y: float + z: float + + def __init__(self, x: float, y: float, z: float) -> None: + self.x = x + self.y = y + self.z = z + + @staticmethod + def from_dict(obj: Any) -> 'Coords': + assert isinstance(obj, dict) + x = from_float(obj.get("x")) + y = from_float(obj.get("y")) + z = from_float(obj.get("z")) + return Coords(x, y, z) + + def to_dict(self) -> dict: + result: dict = {} + result["x"] = to_float(self.x) + result["y"] = to_float(self.y) + result["z"] = to_float(self.z) + return result + + def vector(self): + return App.Vector(self.x, self.y, self.z) + + +class MotionResultModel: + id: str + euler: Coords + position: Coords + + def __init__(self, id: str, euler: Coords, position: Coords) -> None: + self.id = id + self.euler = euler + self.position = position + + @staticmethod + def from_dict(obj: Any) -> 'MotionResultModel': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + euler = Coords.from_dict(obj.get("euler")) + position = Coords.from_dict(obj.get("position")) + return MotionResultModel(id, euler, position) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["euler"] = to_class(Coords, self.euler) + result["position"] = to_class(Coords, self.position) + return result + + +class SequencesEvaluation: + sequences: ISequencesUnion + assemblyDir: str + result: dict = {} + + def __init__(self, sequences, assemblyDir) -> None: + self.sequences = sequences + self.assemblyDir = assemblyDir + pass + + def assemblyComputed(self): + debug = True + for sequenceNumber, v in self.sequences.items(): + for assemblyNumber, assemblySequenced in v.items(): + # print(assemblyNumber) + # print() + # if(assemblyNumber == 1 and sequenceNumber == 4 or assemblyNumber == 1 and sequenceNumber == 0): + if(assemblyNumber == 1 and sequenceNumber == 1 and debug): + debug = False + if(sequenceNumber == 0): + sequenceNumber+=1 + print(assemblySequenced) + self.comptedAssembly( + assemblySequenced, sequenceNumber, assemblyNumber) + + pass + + def comptedAssembly(self, assembly: list[str], sequenceNumber: int, assemblyNumber: int): + assemblyParts = [] + for counter in range(len(assembly)): + importObjAtPath( + self.assemblyDir + 'sdf/meshes/' + assembly[counter] + '.obj', + counter + ) + assemblyParts.append({ + "part": App.ActiveDocument.Objects[counter], + "name": assembly[counter] + }) + + motionResult = json.loads((open(self.assemblyDir + 'stability' + '/' + str( + sequenceNumber + 1) + '/' + str(assemblyNumber) + '/' + 'motion_result.json')).read()) + + simulatorMotionResults: list['MotionResultModel'] = [] + for _k, v in motionResult.items(): + simulatorMotionResults.append(MotionResultModel.from_dict(v)) + + for el in simulatorMotionResults: + for e in assemblyParts: + # сопоставляем детали + if (el.id == e.get('name')): + # вычисляем центр детали для перемещения + center = e.get('part').Shape.CenterOfMass + # получаем центр деталей из симуляции + new_center = App.Vector(el.position.vector()) + + # вычисляем вектор смещения + offset = new_center - center + # перемещаем деталь на вектор смещения + e.get('part').Placement.Base += offset + + # импортируем меш связанный с зоной обьекта + # zonePart = importObjAtPath(self.assemblyDir + "stability/zones/meshes/zone_sub_assembly" + str( + # assembly.__len__() - 1) + ".obj", len(App.ActiveDocument.Objects)) + # получаем координаты зоны относительно детали за которой закреплена зона + print(assemblyNumber) + coords = json.loads( + (open(self.assemblyDir + "stability/zones/sub_assembly_coords_" + str(assemblyNumber - 1) + ".json")).read()) + assemblyCounter = len(assemblyParts) + detailWhichZoneBindings = assemblyParts[assemblyCounter - 2].get( + 'part') + + detailStabilityComputed = assemblyParts[assemblyCounter - 1].get( + 'part') + relativeCoordinates = coords.get('relativeCoordinates') + + relativeEuler = coords.get('relativeEuler') + + specificEuler = { + 'yaw': detailWhichZoneBindings.Placement.Rotation.toEuler()[0] - relativeEuler.get('yaw'), + 'pitch': detailWhichZoneBindings.Placement.Rotation.toEuler()[1] - relativeEuler.get('pitch'), + 'roll': detailWhichZoneBindings.Placement.Rotation.toEuler()[2] - relativeEuler.get('roll') + } + quaternion = euler_to_quaternion(specificEuler.get( + 'yaw'), specificEuler.get('pitch'), specificEuler.get('roll')) + rotation = App.Rotation( + quaternion[0], quaternion[1], quaternion[2], quaternion[3]) + + detailStabilityComputed.Placement.Rotation = rotation + centerVector = detailWhichZoneBindings.Shape.CenterOfMass + vector = App.Vector(relativeCoordinates.get( + 'x'), relativeCoordinates.get('y'), relativeCoordinates.get('z')) + + # current_center = zonePart.Shape.CenterOfMass + # move_vector = App.Vector(centerVector + vector) - current_center + # zonePart.Placement.move(move_vector) + + # computedStabilityResult = computedStability( + # zonePart, detailStabilityComputed) + + if sequenceNumber not in self.result.keys(): + self.result[sequenceNumber] = [] + + # self.result[sequenceNumber].append({ + # str(assemblyNumber): assembly, + # "result": computedStabilityResult + # }) + + # for part in App.ActiveDocument.Objects: + # App.ActiveDocument.removeObject(part.Name) + + +def get_part_center(part): + shape = None + if not hasattr(part, 'Shape'): + shape = part.Mesh + if hasattr(part, 'Shape'): + shape = part.Shape + + center = shape.BoundBox.Center + return App.Vector(center[0], + center[1], + center[2]) + + +def move_second_part_to_match_center(first_part, second_part): + first_center = get_part_center(first_part) + second_center = get_part_center(second_part) + offset = first_center - second_center + second_part.Placement.move(offset) + + +def create(part): + clone = Draft.make_clone([part], forcedraft=True) + + clone.Scale = App.Vector(1.30, 1.30, 1.30) + clone_corr = (App.Vector(0.4476673941774023, -2.109332894191716, -0.5918687740295264) - + clone.Placement.Base).scale(*App.Vector(-0.25, -0.25, -0.25)) + clone.Placement.move(clone_corr) + App.ActiveDocument.recompute() + + return clone def getFullPathObj(assemblyFolder: str, name: str): @@ -27,67 +278,128 @@ def computedStability(refElement, childElement): childElement.Name).getLinkedObject(True).ViewObject, 'DisplayMode', App.ActiveDocument.getObject('Common').ViewObject.DisplayMode) App.ActiveDocument.recompute() obj = App.ActiveDocument.getObjectsByLabel('Common')[0] - + shp = obj.Shape bbox = shp.BoundBox - # Если после операции пересечения зона обьекта совпадает с зоной тестируемого обьекта то тест прошел успешно + # Если после операции пересечения зона обьекта совпадает с зоной тестируемого обьекта то тест прошел успешно if bbox.XLength == rootElement.XLength and bbox.YLength == rootElement.YLength and rootElement.ZLength == bbox.ZLength: return True return False +def autoStabilityZoneComputed(stepFilesPaths: list[str], directoryStableZonesPath: str): + + cadObjects = [] + + for count in range(len(stepFilesPaths)): + + importObjAtPath(stepFilesPaths[count], count) + cadObjects.append(App.ActiveDocument.Objects[count]) + + assemblesBindings = [] + + for increment in range(len(cadObjects)): + if (increment != 0): + detailForEvaluationZ = cadObjects[increment] + zoneBindingDetailZ = cadObjects[increment-1] + assemblesBindings.append( + {'zoneBindingDetail': detailForEvaluationZ, 'detailForEvaluation': zoneBindingDetailZ, 'relativeCoordinates': None, 'zonePart': None}) + for increment in range(len(assemblesBindings)): + + el = assemblesBindings[increment] + + zoneBindingDetail = el.get('zoneBindingDetail') + zoneBindingDetailCenterVector = zoneBindingDetail.Shape.CenterOfMass + zoneDetail = create(el.get('detailForEvaluation')) + + move_second_part_to_match_center( + el.get('zoneBindingDetail'), zoneDetail) + zoneDetail.Label = 'zone_sub_assembly' + str(increment + 1) + + zoneDetail.ViewObject.ShapeColor = (0.40, 0.74, 0.71) + zoneDetail.ViewObject.Transparency = 50 + zoneDetailCenterVector = el.get( + 'detailForEvaluation').Shape.CenterOfMass + + el['relativeCoordinates'] = { + 'x': zoneBindingDetailCenterVector.x - zoneDetailCenterVector.x, + 'y': zoneBindingDetailCenterVector.y - zoneDetailCenterVector.y, + 'z': zoneBindingDetailCenterVector.z - zoneDetailCenterVector.z + } + + el['relativeEuler'] = { + 'yaw': zoneBindingDetail.Placement.Rotation.toEuler()[0] - el.get('detailForEvaluation').Placement.Rotation.toEuler()[0], + 'pitch': zoneBindingDetail.Placement.Rotation.toEuler()[1] - el.get('detailForEvaluation').Placement.Rotation.toEuler()[1], + 'roll': zoneBindingDetail.Placement.Rotation.toEuler()[2] - el.get('detailForEvaluation').Placement.Rotation.toEuler()[2] + } + el['zonePart'] = zoneDetail + + meshesPath = directoryStableZonesPath + 'meshes/' + if not os.path.exists(directoryStableZonesPath): + os.makedirs(directoryStableZonesPath) + if not os.path.exists(meshesPath): + os.makedirs(meshesPath) + zonesSaved = {} + for counter in range(len(assemblesBindings)): + zoneComputed = assemblesBindings[counter] + mesh = zoneComputed.get('zonePart') + zonesSavePath = meshesPath + mesh.Label + '.obj' + importOBJ.export([mesh], zonesSavePath) + zonesSaved[mesh.Label] = 'meshes/' + mesh.Label + '.obj' + for counter in range(len(assemblesBindings)): + el = assemblesBindings[counter] + savePath = zonesSaved[el.get('zonePart').Label] + el['zonePart'] = savePath + el['detailForEvaluation'] = el['detailForEvaluation'].Label + el['zoneBindingDetail'] = el['zoneBindingDetail'].Label + + json_result = json.dumps(el) + + file_to_open = directoryStableZonesPath + \ + 'sub_assembly_coords_' + str(counter) + '.json' + + f = open(file_to_open, 'w', ) + + f.write(json_result) + f.close() + + def main(): App.newDocument() - # загрузка env интерфейса env = json.loads((open('./env.json')).read()) - # получение пути координат файла - coordinatesFilePath = env.get('pathToTheSimulationCoordinatesFile') + assemblyDir = env.get('aspPath') + sequencesJSON = json.loads((open(assemblyDir + 'sequences.json')).read()) + directoryStableZones = assemblyDir + 'stability/zones/' - # получение папки сборки - assemblyFolder = env.get('generationFolder') - buildNumber = int(re.findall(r'\d', coordinatesFilePath)[0]) + sequences = sequencesJSON.get('sequences') + stepStructure = json.loads( + (open(assemblyDir + 'step-structure.json')).read()) - # загрузка последовательности сборки ввиде списка строк деталей - assemblyStructure = json.loads( - (open(assemblyFolder + 'step-structure.json')).read()) - # получение номера тестируемой сборки - assemblyNumber = int(buildNumber) - # получение тестируемой детали в сборке - activeDetail = assemblyStructure[assemblyNumber] - - subassemblyNotParticipatingInMarkup = assemblyStructure[0:assemblyNumber - 1] - detailOfTheMarkingZoneOfWhich = assemblyStructure[assemblyNumber - 1] - importObjAtPath(getFullPathObj(assemblyFolder, activeDetail)) - importObjAtPath(getFullPathObj( - assemblyFolder, detailOfTheMarkingZoneOfWhich)) - meshMark = App.ActiveDocument.Objects[0] - meshDetailOfTheMarkZone = App.ActiveDocument.Objects[1] - meshMark.ViewObject.ShapeColor = (0.31, 0.77, 0.87) - meshDetailOfTheMarkZone.ViewObject.ShapeColor = (0.68, 0.66, 0.95) - for el in list(map(lambda el: getFullPathObj(assemblyFolder, el), subassemblyNotParticipatingInMarkup)): - importObjAtPath(el) - for el in App.ActiveDocument.Objects[2:App.ActiveDocument.Objects.__len__()]: - el.ViewObject.ShapeColor = (0.32, 0.05, 0.38) - # загрузка координат - coordinates = json.loads((open(coordinatesFilePath)).read()) - inc = 1 - # перемещение обьектов в документе на точки стабильность полученные из pybullet - for el in App.ActiveDocument.Objects: - pos = coordinates[inc]['position'] - qua = coordinates[inc]['quaternion'] - new_quaternion = Quaternion(qua[0], qua[1], qua[2], qua[3]) - rotation_matrix = new_quaternion.rotation_matrix - current_position = el.Placement.Base - new_position = App.Vector( - current_position.x, current_position.y, current_position.z) - new_placement = App.Placement(new_position, rotation_matrix) - el.Placement = new_placement - App.ActiveDocument.recompute() - el.Placement.move(App.Vector(pos[0], pos[1], pos[2])) - # вычисление стабильность - computedStability(meshMark, meshDetailOfTheMarkZone) - pass + stepFilesPaths = [] + for step in stepStructure: + stepFilesPaths.append(assemblyDir+'sdf/meshes/' + step + '.obj') + if not os.path.exists(directoryStableZones): + print('Zones not found automatic calculation started') + autoStabilityZoneComputed(stepFilesPaths, directoryStableZones) + + sequencesJoin = {} + + for arrayCounter in range(len(sequences)): + for indexCounter in range(len(sequences[arrayCounter])): + if (indexCounter != 0): + if (sequencesJoin.get(arrayCounter) == None): + sequencesJoin[arrayCounter] = { + indexCounter: sequences[arrayCounter][0:indexCounter+1] + } + else: + sequencesJoin[arrayCounter][indexCounter] = sequences[arrayCounter][0:indexCounter+1] + + + + seqEvaluation = SequencesEvaluation(sequencesJoin, assemblyDir) + seqEvaluation.assemblyComputed() + print(seqEvaluation.result) main() diff --git a/geometric_feasibility_predicate/env.json b/geometric_feasibility_predicate/env.json index 4bc2d96..9c62939 100644 --- a/geometric_feasibility_predicate/env.json +++ b/geometric_feasibility_predicate/env.json @@ -1,4 +1,4 @@ { - "cadFilePath":"/home/idontsudo/framework/geometric_feasibility_predicate/cubes.FCStd", - "outPath":"/home/idontsudo/framework/geometric_feasibility_predicate/out/" + "cadFilePath":"/home/idontsudo/framework/asp/out/disk_and_axis_n (2).FCStd", + "outPath":"/home/idontsudo/framework/asp/out/" } \ No newline at end of file diff --git a/geometric_feasibility_predicate/main.py b/geometric_feasibility_predicate/main.py index 7e717fa..d8840ab 100644 --- a/geometric_feasibility_predicate/main.py +++ b/geometric_feasibility_predicate/main.py @@ -189,7 +189,7 @@ class AdjacencyMatrix: @staticmethod def from_dict(obj: Any) -> 'AdjacencyMatrix': assert isinstance(obj, dict) - all_pars = from_list(from_str, obj.get("allPars")) + all_pars = from_list(from_str, obj.get("allParts")) first_detail = from_str(obj.get("firstDetail")) matrix = from_dict(lambda x: from_list(from_str, x), obj.get("matrix")) @@ -197,7 +197,7 @@ class AdjacencyMatrix: def to_dict(self) -> dict: result: dict = {} - result["allPars"] = from_list(from_str, self.all_parts) + result["allParts"] = from_list(from_str, self.all_parts) result["firstDetail"] = from_str(self.first_detail) result["matrix"] = from_dict( lambda x: from_list(from_str, x), self.matrix) @@ -449,13 +449,54 @@ class CadAdjacencyMatrix: part.Shape.distToShape(nextPart.Shape)[0]) if (collisionResult == 0): matrix[part.Label].append(nextPart.Label) - return AdjacencyMatrix(all_parts=GetAllPartsLabelsUseCase( ).call(), first_detail=GetFirstDetailUseCase().call(), matrix=matrix ) +def reduce(function, iterable, initializer=None): + it = iter(iterable) + if initializer is None: + value = next(it) + else: + value = initializer + for element in it: + value = function(value, element) + return value + + +def to_ascii_hash(text): + ascii_values = [ord(character) for character in text] + return reduce(lambda x, y: x + y, ascii_values) + + +def matrixGetUniqueContact(matrix): + + detailsToCheck = [] + detailsHashCheck = {} + for k, v in matrix.items(): + for el in v: + if (el != k): + hash = to_ascii_hash(k + el) + if (detailsHashCheck.get(hash) == None): + detailsHashCheck[hash] = hash + detailsToCheck.append({ + 'child': el, + 'parent': k + }) + return detailsToCheck + +def intersectionComputed(parts): + App.activeDocument().addObject("Part::MultiCommon", "Common") + App.activeDocument().Common.Shapes = [parts[0], parts[1]] + App.activeDocument().getObject('Common').ViewObject.ShapeColor = getattr(parts[0].getLinkedObject(True).ViewObject, 'ShapeColor', App.activeDocument().getObject('Common').ViewObject.ShapeColor) + App.activeDocument().getObject('Common').ViewObject.DisplayMode = getattr(parts[0].getLinkedObject(True).ViewObject, 'DisplayMode', App.activeDocument().getObject('Common').ViewObject.DisplayMode) + App.ActiveDocument.recompute() + area = App.activeDocument().getObject('Common').Shape.Area + App.ActiveDocument.removeObject('Common') + return area + def main(): env = FS.readJSON('env.json') cadFile = env['cadFilePath'] @@ -464,14 +505,34 @@ def main(): return TypeError('CadFile not found env.json') App.open(u'' + cadFile) - # Получение матрицы - topologyMatrix = CadAdjacencyMatrix().primitiveMatrix() + topologyMatrix = CadAdjacencyMatrix().matrixBySurfaces() import json sequences = json.dumps({"sequences": AllSequences( topologyMatrix.matrix).adj_matrix_names}, ensure_ascii=False, indent=4) + matrix = topologyMatrix.matrix + contacts = matrixGetUniqueContact(matrix) + intersection_geometry = { + 'status':True, + 'recalculations':None + } + for el in contacts: + child = App.ActiveDocument.getObjectsByLabel(el.get('child'))[0] + parent = App.ActiveDocument.getObjectsByLabel(el.get('parent'))[0] + area = intersectionComputed([child,parent]) + if(area != 0.0): + if(intersection_geometry.get('recalculations') == None): + intersection_geometry['status'] = False + intersection_geometry['recalculations'] = [] + intersection_geometry['recalculations'].append({ + 'area':area, + 'connect': el.get('child') + ' ' + el.get('parent') + }) + + + FS.writeFile(json.dumps(intersection_geometry, ensure_ascii=False, indent=4), outPath, 'intersection_geometry.json') FS.writeFile(sequences, outPath, 'sequences.json') FS.writeFile(json.dumps(topologyMatrix.to_dict(), ensure_ascii=False, indent=4), outPath, 'adjacency_matrix.json') - - + main() + \ No newline at end of file diff --git a/pddl/helper/fs.py b/pddl/helper/fs.py index 1bd3dbe..bcd33e6 100644 --- a/pddl/helper/fs.py +++ b/pddl/helper/fs.py @@ -3,15 +3,15 @@ import json class FS: def readJSON(path:str): - return json.loads((open(path)).read()) + f = open(path) + return json.loads((f).read()) def writeFile(data, filePath, fileName): - file_to_open = filePath + fileName - - f = open(file_to_open, 'w', ) - + f = open(file_to_open, 'w') f.write(data) + f.close() + def readFile(path): return open(path).read() diff --git a/pddl/main.py b/pddl/main.py index 9c05f1c..0856cf0 100644 --- a/pddl/main.py +++ b/pddl/main.py @@ -5,7 +5,8 @@ from src.model.asm4_structure import Asm4Structure from src.usecases.assembly_to_pddl_use_case import AssemblyToPddlUseCase -# python3 main.py --stepStructurePath /Users/idontsudo/robo/step-structure.json --outPath /Users/idontsudo/robo/pddl/out/ +# python3 main.py --stepStructurePath --outPath /Users/idontsudo/robo/pddl/out/ +# python3 main.py --stepStructurePath /Users/idontsudo/framework/asp/out/step-structure.json --outPath /Users/idontsudo/robo/pddl/out/ if __name__ == "__main__": parser = argparse.ArgumentParser() diff --git a/stability_process_predicate/main.py b/stability_process_predicate/main.py index 06028a9..89ab934 100644 --- a/stability_process_predicate/main.py +++ b/stability_process_predicate/main.py @@ -1,8 +1,7 @@ import argparse from usecases.stability_check_usecase import StabilityCheckUseCase - -# python3 main.py --aspPath /Users/idontsudo/framework/asp/out/ +# python3 main.py --aspPath /Users/idontsudo/framework/asp/out def main(): parser = argparse.ArgumentParser() @@ -10,3 +9,4 @@ def main(): args = parser.parse_args() StabilityCheckUseCase().call(args.aspPath) main() + \ No newline at end of file diff --git a/stability_process_predicate/usecases/stability_check_usecase.py b/stability_process_predicate/usecases/stability_check_usecase.py index 86d6004..da35575 100644 --- a/stability_process_predicate/usecases/stability_check_usecase.py +++ b/stability_process_predicate/usecases/stability_check_usecase.py @@ -116,7 +116,6 @@ class StabilityCheckUseCase: f.write(urdf) f.close() urdfs.append(os.path.abspath(f.name)) - return urdfs def executeSimulation(self, assembly: list[str], outPath: str, urdfGeneration: dict[str:str], duration: int) -> list['SimulatorStabilityResultModel']: @@ -133,7 +132,6 @@ class StabilityCheckUseCase: for el in urdfs: id = p.loadURDF(el) bulletIds.append(id) - for i in range(duration): if (i + 200 == duration): inc = 0