예제 #1
0
 def test_revise_one_parent_post_unselected(self):
     """
     Tests a post request to revise a part which has one parent.
     This parent is not selected, and so its bom should not change.
     """
     parent = PartController.create("RefParent", "Part", "a", self.user,
                                    self.DATA)
     parent.add_child(self.controller, 10, 25, "-")
     link = parent.get_children(1)[0].link
     data = {
         "revision": "b",
         "parents-TOTAL_FORMS": "1",
         "parents-INITIAL_FORMS": "1",
         "parents-0-selected": "",
         "parents-0-link": link.id,
         "parents-0-new_parent": parent.id,
         "children-TOTAL_FORMS": "0",
         "children-INITIAL_FORMS": "0",
         "documents-TOTAL_FORMS": "0",
         "documents-INITIAL_FORMS": "0",
     }
     response = self.post(self.base_url + "revisions/", data)
     revisions = self.controller.get_next_revisions()
     self.assertEqual(1, len(revisions))
     rev = revisions[0].part
     self.assertEqual("b", rev.revision)
     # ensure the old revision is still a child of the parent
     children = parent.get_children(1)
     self.assertEqual([(1, link)], children)
     self.assertFalse(PartController(rev, self.user).get_parents())
예제 #2
0
 def get_all_geometry_files(self, doc_file):
     if self.PartDecompose is not None:
         pctrl = PartController(self.PartDecompose, self._user)
         if self._stps is None:
             children_ids = [
                 c.link.child_id
                 for c in pctrl.get_children(-1,
                                             related=("child__id"),
                                             only=(
                                                 "child__id",
                                                 "parent__id",
                                             ))
             ]
             if children_ids:
                 docs = pmodels.DocumentPartLink.objects.now().filter(
                     document__type="Document3D",
                     part__in=children_ids).values_list("document",
                                                        flat=True)
                 dfs = pmodels.DocumentFile.objects.filter(document__in=docs, deprecated=False)\
                         .filter(is_stp).values_list("id", flat=True)
                 self._stps = dfs
             else:
                 self._stps = pmodels.DocumentFile.objects.none(
                 ).values_list("id", flat=True)
         q = Q(stp=doc_file)
         stps = list(self._stps)
         if stps:
             q |= Q(stp__in=stps)
         gfs = GeometryFile.objects.filter(q)
     else:
         gfs = GeometryFile.objects.filter(stp=doc_file)
     return gfs.values_list("file", flat=True)
예제 #3
0
 def test_revise_one_child_post_unselected(self):
     """
     Tests a post request to revise a part with child which is not selected.
     """
     child = PartController.create("RefChild", "Part", "a", self.user,
                                   self.DATA)
     self.controller.add_child(child, 10, 25, "-")
     link = self.controller.get_children(1)[0].link
     data = {
         "revision": "b",
         "parents-TOTAL_FORMS": "0",
         "parents-INITIAL_FORMS": "0",
         "children-TOTAL_FORMS": "1",
         "children-INITIAL_FORMS": "1",
         "children-0-selected": "",
         "children-0-link": link.id,
         "documents-TOTAL_FORMS": "0",
         "documents-INITIAL_FORMS": "0",
     }
     response = self.post(self.base_url + "revisions/", data)
     revisions = self.controller.get_next_revisions()
     self.assertEqual(1, len(revisions))
     rev = revisions[0].part
     self.assertEqual("b", rev.revision)
     # ensure the part is still a child of the old revision
     children = self.controller.get_children(1)
     self.assertEqual([(1, link)], children)
     # ensure the part is not a child of the new revision
     children = PartController(rev, self.user).get_children(1)
     self.assertEqual(0, len(children))
예제 #4
0
    def test_update_no_checkin(self):
        ctrl = self.create("d1", "Document3D")
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.build_assembly(_ASSEMBLY1, natives, steps, False)

        tree = copy.deepcopy(_UPDATED_ASSEMBLY1)
        tree["children"][0]["document"]["checkin"] = False
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native")
        steps = get_steps("bolt.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.update_assembly(tree, natives, steps)
        self.assertEqual(5, models.Part.objects.all().count())
        self.assertEqual(5, models.Document.objects.all().count())
        # root
        root = PartController(ctrl.PartDecompose, self.user)
        self.assertDoc(root.object, "test.step", "test.native_asm")
        self.assertEqual("test", root.name)
        children = root.get_children(-1)
        self.assertEqual(4, len(children))
        # l-bracket
        pcl = children[0].link
        child = pcl.child
        self.assertLink(pcl, "L-BRACKET", root.object, 1, 1)
        self.assertDoc(child, u"l-bracket.step", u"L-BRACKET.native")
        # nba_asm
        pcl = children[1].link
        child = nba_asm = pcl.child
        self.assertLink(pcl, "NBA_ASM", root.object, 2, 3)
        self.assertDoc(child, u"NBA_ASM.step", u"NBA_ASM.native_asm")
        # bolt
        pcl = children[2].link
        child = pcl.child
        self.assertLink(pcl, "BOLT", nba_asm, 1, 2)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        # nut
        pcl = children[3].link
        child = pcl.child
        self.assertLink(pcl, "NUT", nba_asm, 2, 1)
        self.assertDoc(child, u"nut.step", u"NUT.native")

        # minor revisions of document files
        for doc in models.Document.objects.all():
            for f in doc.files:
                if doc.name == "L-BRACKET":
                    self.assertEqual(1, f.revision)
                else:
                    self.assertEqual(2, f.revision)
        # check product is valid
        self.assertProduct(ctrl)
예제 #5
0
    def _add_children(self, component, root):
        pctrl = PartController(root, self.user)
        pctrl.check_readable()
        children = pctrl.get_children(-1)
        if not children:
            return

        components = { root.id: component, }
        links = [c.link for c in children]
        locations = defaultdict(list) # pcl -> locations
        for loc in Location_link.objects.filter(link__in=links):
            locations[loc.link_id].append(loc)
        parts = [l.child_id for l in links]
        part2doc = {}
        for doc in Document3D.objects.filter(PartDecompose__in=parts):
            key = doc.PartDecompose_id
            # if two revisions are present, takes the newest one
            otherdoc = part2doc.get(key)
            if otherdoc is None or otherdoc.ctime < doc.ctime:
                part2doc[key] = doc

        visited_links = set()
        mtime = root.ctime
        for link in links:
            if link.id in visited_links:
                pass
            try:
                comp = components[link.parent_id]
                part = link.child
                doc = part2doc[part.id]
                locs = locations[link.id]
            except KeyError:
                # it is not an interessing link
                pass
            else:
                mtime = max(mtime, link.ctime)
                child_comp = self._get_component(doc, part)
                locs = [{"local_name": l.name, "local_matrix": l.to_array()} for l in locs]
                comp["children"].append({
                    "component": child_comp,
                    "locations": locs,
                })
                components[part.id] = child_comp
                visited_links.add(link.id)

        self._allowed_checkout = self._get_checkout(mtime)
예제 #6
0
    def test_update_assembly_locations(self):
        ctrl = self.create("d1", "Document3D")
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.build_assembly(_ASSEMBLY1, natives, steps, False)

        tree = copy.deepcopy(_UPDATED_ASSEMBLY1)
        m1 = [1, 7, 5, 9, 5, 4, 3, 2, 2, 0, 5, 1]
        tree["children"][0]["local_matrix"] = m1
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.update_assembly(tree, natives, steps)
        self.assertEqual(5, models.Document.objects.all().count())
        self.assertEqual(5, models.Part.objects.all().count())
        # root
        root = PartController(ctrl.PartDecompose, self.user)
        self.assertDoc(root.object, "test.step", "test.native_asm")
        self.assertEqual("test", root.name)
        children = root.get_children(-1)
        self.assertEqual(4, len(children))
        # l-bracket
        pcl = children[0].link
        child = pcl.child
        self.assertLink(pcl, "L-BRACKET", root.object, 1, 1)
        self.assertEqual(m1, pcl.extensions[0].to_array())
        self.assertDoc(child, u"l-bracket.step", u"L-BRACKET.native")
        # nba_asm
        pcl = children[1].link
        child = nba_asm = pcl.child
        self.assertLink(pcl, "NBA_ASM", root.object, 2, 3)
        self.assertDoc(child, u"NBA_ASM.step", u"NBA_ASM.native_asm")
        # bolt
        pcl = children[2].link
        child = pcl.child
        self.assertLink(pcl, "BOLT", nba_asm, 1, 2)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        # nut
        pcl = children[3].link
        child = pcl.child
        self.assertLink(pcl, "NUT", nba_asm, 2, 1)
        self.assertDoc(child, u"nut.step", u"NUT.native")

        # check product is valid
        self.assertProduct(ctrl)
예제 #7
0
    def test_build_assembly(self):
        self.assertEqual(0, models.PLMObject.objects.all().count())
        ctrl = self.create("d1", "Document3D")
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        df = builder.build_assembly(_ASSEMBLY1, natives, steps, False)

        self.assertEqual(5, models.Document.objects.all().count())
        self.assertEqual(5, models.Part.objects.all().count())
        # root
        self.assertEqual("test.native_asm", df.filename)
        root = PartController(ctrl.PartDecompose, self.user)
        self.assertDoc(root.object, "test.step", "test.native_asm")
        self.assertEqual("test", root.name)
        children = root.get_children(-1)
        self.assertEqual(4, len(children))
        # l-bracket
        pcl = children[0].link
        child = pcl.child
        self.assertLink(pcl, "L-BRACKET", root.object, 1, 1)
        self.assertEqual(
            [4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
            pcl.extensions[0].to_array())
        self.assertDoc(child, u"l-bracket.step", u"L-BRACKET.native")
        # nba_asm
        pcl = children[1].link
        child = nba_asm = pcl.child
        self.assertLink(pcl, "NBA_ASM", root.object, 2, 3)
        self.assertDoc(child, u"NBA_ASM.step", u"NBA_ASM.native_asm")
        # bolt
        pcl = children[2].link
        child = pcl.child
        self.assertLink(pcl, "BOLT", nba_asm, 1, 1)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        # nut
        pcl = children[3].link
        child = pcl.child
        self.assertLink(pcl, "NUT", nba_asm, 2, 1)
        self.assertDoc(child, u"nut.step", u"NUT.native")

        # check product is valid
        self.assertProduct(ctrl)
예제 #8
0
    def test_revise_two_childrens_post(self):
        """
        Tests a post request to revise a part with two children.
        Only one child is selected.
        """
        child1 = PartController.create("RefChild1", "Part", "a", self.user,
                                       self.DATA)
        self.controller.add_child(child1, 10, 25, "-")
        child2 = PartController.create("RefChild2", "Part", "a", self.user,
                                       self.DATA)
        self.controller.add_child(child2, 10, 55, "-")

        link1, link2 = [c.link for c in self.controller.get_children(1)]
        data = {
            "revision": "b",
            "parents-TOTAL_FORMS": "0",
            "parents-INITIAL_FORMS": "0",
            "children-TOTAL_FORMS": "1",
            "children-INITIAL_FORMS": "1",
            "children-0-selected": "on",
            "children-0-link": link1.id,
            "children-1-selected": "",
            "children-1-link": link2.id,
            "documents-TOTAL_FORMS": "0",
            "documents-INITIAL_FORMS": "0",
        }
        response = self.post(self.base_url + "revisions/", data)
        revisions = self.controller.get_next_revisions()
        self.assertEqual(1, len(revisions))
        rev = revisions[0].part
        self.assertEqual("b", rev.revision)
        # ensure the part is still a child of the old revision
        children = self.controller.get_children(1)
        self.assertEqual(set(((1, link1), (1, link2))), set(children))
        children = PartController(rev, self.user).get_children(1)
        self.assertEqual(1, len(children))

        link = children[0].link
        self.assertEqual(link1.child, link.child)
        self.assertEqual(link1.order, link.order)
        self.assertEqual(link1.quantity, link.quantity)
        self.assertEqual(link1.unit, link.unit)
예제 #9
0
    def test_attach(self):
        expected = []
        result_part = []
        result_doc = []
        for pstate, dstate, powner, downer, can_attach in self.MATRICE:
            self.part.object.state = self.states[pstate]
            self.part.object.lifecycle = self.lifecycles[pstate]
            self.doc.object.state = self.states[dstate]
            self.doc.object.lifecycle = self.lifecycles[dstate]
            self.part.set_owner(self.user if powner else self.other_owner,
                                True)
            self.doc.set_owner(self.user if downer else self.other_owner, True)

            expected.append(can_attach)
            pctrl = PartController(self.part.object, self.user)
            result_part.append(pctrl.can_attach_document(self.doc.object))
            dctrl = DocumentController(self.doc.object, self.user)
            result_doc.append(dctrl.can_attach_part(self.part.object))
        self.assertEqual(expected, result_part)
        self.assertEqual(expected, result_doc)
예제 #10
0
    def get_product(self, doc_file, recursive=False):
        """
        Returns the :class:`.Product` associated to *doc_file*.
        If *recursive* is True, it returns a complet product, built by browsing
        the BOM of the attached part, if it has been decomposed.
        """
        try:
            af = ArbreFile.objects.get(stp=doc_file)
        except:
            return None
        product = classes.Product.from_list(json.loads(af.file.read()))
        if recursive and product:
            if self.PartDecompose is not None:
                # Here be dragons
                # this code try to reduce the number of database queries:
                # h queries (h: height of the BOM) to get children
                # + 1 query to doc-part links
                # + 1 query to get STP files
                # + 1 query to get location links
                # + 1 query to get ArbreFile
                pctrl = PartController(self.PartDecompose, self._user)
                children = pctrl.get_children(-1,
                                              related=("child__id"),
                                              only=(
                                                  "child__id",
                                                  "parent__id",
                                              ))
                if not children:
                    return product
                links, children_ids = zip(*[(c.link.id, c.link.child_id)
                                            for c in children])
                docs = []
                part_to_docs = defaultdict(list)
                for doc, part in pmodels.DocumentPartLink.current_objects.filter(
                        document__type="Document3D",
                        part__in=children_ids).values_list(
                            "document", "part").order_by("-ctime"):
                    # order by -ctime to test the most recently attached document first
                    part_to_docs[part].append(doc)
                    docs.append(doc)
                if not docs:
                    return product

                dfs = dict(pmodels.DocumentFile.objects.filter(document__in=docs, deprecated=False)\
                        .filter(is_stp).values_list("document", "id"))
                # cache this values as it may be useful for get_all_geometry_files
                self._stps = dfs.values()
                locs = defaultdict(list)
                for l in Location_link.objects.filter(link__in=links):
                    locs[l.link_id].append(l)
                # read all jsons files
                jsons = {}
                for af in ArbreFile.objects.filter(stp__in=dfs.values()):
                    jsons[af.stp_id] = json.loads(af.file.read())
                # browse the BOM and build product
                previous_level = 0
                products = [product]
                for level, link in children:
                    if level <= previous_level:
                        del products[level:]
                    stp = None
                    for doc in part_to_docs[link.child_id]:
                        if doc in dfs:
                            stp = dfs[doc]
                            break
                    if stp is not None and stp in jsons:
                        pr = products[-1]
                        prod = classes.Product.from_list(
                            jsons[stp],
                            product=False,
                            product_root=product,
                            deep=level,
                            to_update_product_root=pr)
                        for location in locs[link.id]:
                            pr.links[-1].add_occurrence(
                                location.name, location)
                        products.append(prod)
                    previous_level = level

        return product
예제 #11
0
    def test_update_add_existing_rod(self):
        rod = self.create("rod", "Document3D")
        rod.name = "ROD"
        rod.save()
        rod_step = rod.add_file(get_steps("rod.step")[0])
        rod_native = rod.add_file(get_natives("ROD.native")[0])
        rod_part = PartController.create("rod", "Part", "a", self.user, {
            "group": self.group,
            "lifecycle": rod.lifecycle,
            "name": rod.name
        })
        rod.attach_to_part(rod_part)
        rod.PartDecompose = rod_part.object
        rod.save()

        ctrl = self.create("d1", "Document3D")
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native",
                              "ROD.native")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step",
                          "rod.step")
        builder = AssemblyBuilder(ctrl)
        builder.build_assembly(_ASSEMBLY1, natives, steps, False)

        tree = copy.deepcopy(_UPDATED_ASSEMBLY1)
        tree["children"].append({
            u'children': [],
            u'local_matrix':
            [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
            u'local_name':
            u'ROD',
            u'native': {
                "id": rod_native.id
            },
            u'part': {
                "id": rod_part.id
            },
            u'document': {
                "id": rod.id,
                "checkin": True
            },
            u'step': {
                "id": rod_step.id
            },
        })
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native",
                              "ROD.native")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step",
                          "rod.step")
        builder = AssemblyBuilder(ctrl)
        builder.update_assembly(tree, natives, steps)
        self.assertEqual(6, models.Part.objects.all().count())
        self.assertEqual(6, models.Document.objects.all().count())
        # root
        root = PartController(ctrl.PartDecompose, self.user)
        self.assertDoc(root.object, "test.step", "test.native_asm")
        self.assertEqual("test", root.name)
        children = root.get_children(-1)
        self.assertEqual(5, len(children))
        # l-bracket
        pcl = children[0].link
        child = pcl.child
        self.assertLink(pcl, "L-BRACKET", root.object, 1, 1)
        self.assertDoc(child, u"l-bracket.step", u"L-BRACKET.native")
        # nba_asm
        pcl = children[1].link
        child = nba_asm = pcl.child
        self.assertLink(pcl, "NBA_ASM", root.object, 2, 3)
        self.assertDoc(child, u"NBA_ASM.step", u"NBA_ASM.native_asm")
        # bolt
        pcl = children[2].link
        child = pcl.child
        self.assertLink(pcl, "BOLT", nba_asm, 1, 2)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        # nut
        pcl = children[3].link
        child = pcl.child
        self.assertLink(pcl, "NUT", nba_asm, 2, 1)
        self.assertDoc(child, u"nut.step", u"NUT.native")
        # rod
        pcl = children[4].link
        child = pcl.child
        self.assertEqual(child.id, rod_part.id)
        self.assertLink(pcl, "ROD", root.object, 3, 1)
        self.assertDoc(child, u"rod.step", u"ROD.native")

        # minor revisions of document files
        for doc in models.Document.objects.all():
            for f in doc.files:
                self.assertEqual(2, f.revision)
        # check product is valid
        self.assertProduct(ctrl)
예제 #12
0
    def test_update_new_assembly(self):
        ctrl = self.create("d1", "Document3D")
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.build_assembly(_ASSEMBLY1, natives, steps, False)

        tree = copy.deepcopy(_UPDATED_ASSEMBLY1)
        tree["children"].append({
            u'children': [
                {
                    u'children': [],
                    u'local_matrix': [
                        1.5, 5.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                        0.0
                    ],
                    u'local_name':
                    u'BOLT',
                    u'native':
                    deferred_file('BOLT.native'),
                    u'part':
                    deferred_part('BOLT'),
                    u'document':
                    deferred_doc('BOLT'),
                    u'step':
                    deferred_file(u'bolt.step'),
                },
                {
                    u'children': [],
                    u'local_matrix': [
                        1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 838.2, 0.0, 0.0,
                        1.0, 0.0
                    ],
                    u'local_name':
                    u'NUT',
                    u'native':
                    deferred_file('NUT.native'),
                    u'part':
                    deferred_part('NUT'),
                    u'document':
                    deferred_doc('NUT'),
                    u'step':
                    deferred_file(u'nut.step'),
                },
            ],
            u'local_matrix':
            [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
            u'local_name':
            u'ROD',
            u'native':
            u'ROD.native_asm',
            u'part_name':
            u'ROD',
        })
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native",
                              "ROD.native_asm")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.update_assembly(tree, natives, steps)
        self.assertEqual(6, models.Part.objects.all().count())
        self.assertEqual(6, models.Document.objects.all().count())
        # root
        root = PartController(ctrl.PartDecompose, self.user)
        self.assertDoc(root.object, "test.step", "test.native_asm")
        self.assertEqual("test", root.name)
        children = root.get_children(-1)
        self.assertEqual(7, len(children))
        # l-bracket
        pcl = children[0].link
        child = pcl.child
        self.assertLink(pcl, "L-BRACKET", root.object, 1, 1)
        self.assertDoc(child, u"l-bracket.step", u"L-BRACKET.native")
        # nba_asm
        pcl = children[1].link
        child = nba_asm = pcl.child
        self.assertLink(pcl, "NBA_ASM", root.object, 2, 3)
        self.assertDoc(child, u"NBA_ASM.step", u"NBA_ASM.native_asm")
        # bolt
        pcl = children[2].link
        child = pcl.child
        self.assertLink(pcl, "BOLT", nba_asm, 1, 2)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        # nut
        pcl = children[3].link
        child = pcl.child
        self.assertLink(pcl, "NUT", nba_asm, 2, 1)
        self.assertDoc(child, u"nut.step", u"NUT.native")
        # rod
        pcl = children[4].link
        rod = child = pcl.child
        self.assertLink(pcl, "ROD", root.object, 3, 1)
        self.assertDoc(child, u"ROD.step", u"ROD.native_asm")
        # rod -> bolt
        pcl = children[5].link
        child = pcl.child
        self.assertLink(pcl, "BOLT", rod, 1, 1)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        # rod -> nut
        pcl = children[6].link
        child = pcl.child
        self.assertLink(pcl, "NUT", rod, 2, 1)
        self.assertDoc(child, u"nut.step", u"NUT.native")

        # minor revisions of document files
        for doc in models.Document.objects.all():
            for f in doc.files:
                if doc.name == "ROD":
                    self.assertEqual(1, f.revision)
                else:
                    self.assertEqual(2, f.revision)
        # check product is valid
        self.assertProduct(ctrl)
예제 #13
0
    def test_update_assembly_more_bolts(self):
        ctrl = self.create("d1", "Document3D")
        natives = get_natives("test.native_asm", "NBA_ASM.native_asm",
                              "NUT.native", "BOLT.native", "L-BRACKET.native")
        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.build_assembly(_ASSEMBLY1, natives, steps, False)

        tree = copy.deepcopy(_UPDATED_ASSEMBLY1)
        m1 = [1, 7, 5, 9, 5, 4, 3, 2, 2, 0, 5, 1]
        tree["children"].append({
            u'children': [],
            u'local_matrix': m1,
            u'local_name': u'BOLT',
            u'native': deferred_file('BOLT.native'),
            u'part': deferred_part('BOLT'),
            u'document': deferred_doc('BOLT'),
            u'step': deferred_file(u'bolt.step'),
        })

        steps = get_steps("bolt.step", "l-bracket.step", "nut.step")
        builder = AssemblyBuilder(ctrl)
        builder.update_assembly(tree, natives, steps)
        self.assertEqual(5, models.Part.objects.all().count())
        self.assertEqual(5, models.Document.objects.all().count())
        # root
        root = PartController(ctrl.PartDecompose, self.user)
        self.assertDoc(root.object, "test.step", "test.native_asm")
        self.assertEqual("test", root.name)
        children = root.get_children(-1)
        self.assertEqual(5, len(children))
        # l-bracket
        pcl = children[0].link
        child = pcl.child
        self.assertLink(pcl, "L-BRACKET", root.object, 1, 1)
        self.assertDoc(child, u"l-bracket.step", u"L-BRACKET.native")
        # nba_asm
        pcl = children[1].link
        child = nba_asm = pcl.child
        self.assertLink(pcl, "NBA_ASM", root.object, 2, 3)
        self.assertDoc(child, u"NBA_ASM.step", u"NBA_ASM.native_asm")
        # bolt
        pcl = children[2].link
        child = pcl.child
        first_bolt = child.id
        self.assertLink(pcl, "BOLT", nba_asm, 1, 2)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        # nut
        pcl = children[3].link
        child = pcl.child
        self.assertLink(pcl, "NUT", nba_asm, 2, 1)
        self.assertDoc(child, u"nut.step", u"NUT.native")
        # new bolt
        pcl = children[4].link
        child = pcl.child
        self.assertLink(pcl, "BOLT", root.object, 3, 1)
        self.assertDoc(child, u"bolt.step", u"BOLT.native")
        self.assertEqual(first_bolt, child.id)

        # minor revisions of document files
        for doc in models.Document.objects.all():
            for f in doc.files:
                self.assertEqual(2, f.revision)
        # check product is valid
        self.assertProduct(ctrl)