def test_write_to_freecad(self):
        active_document = ActiveDocument(
            self._WORKING_DIRECTORY).open_set_and_get_document(
                "PartSheetTest_Write")
        json_part = AJsonPart().parse_from_json(self._json_test_object)
        json_spread_sheet = JsonSpreadSheet(json_part)

        json_spread_sheet_name = json_spread_sheet.create_sheet_name()

        json_part_sheet_object = active_document.app_active_document.getObject(
            json_spread_sheet_name)
        self.assertIsNone(json_part_sheet_object,
                          "The object does not yet exist")

        json_spread_sheet.write_to_freecad(active_document)

        json_part_sheet_object = active_document.app_active_document.getObject(
            json_spread_sheet_name)
        self.assertIsNotNone(json_part_sheet_object,
                             "The object does exist now")
        self.assertEquals(len(active_document.app_active_document.RootObjects),
                          1, "Correct amount of objects in document")
        self.assertEquals(
            len(json_part_sheet_object.PropertiesList), 36,
            "Computed correct amount of properties in the sheet")
    def read_from_freecad(self, freecad_object, freecad_sheet):
        sheet = JsonSpreadSheet(self)

        self.name = sheet.read_sheet_attribute_from_freecad(
            freecad_sheet, "name")
        self.shape = sheet.read_sheet_attribute_from_freecad(
            freecad_sheet, "shape")
        self.uuid = sheet.read_sheet_attribute_from_freecad(
            freecad_sheet, "uuid")

        # initialize with values of the sheet
        self.length = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "length"))
        self.width = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "width"))
        self.height = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "height"))
        self.radius = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "radius"))
        self.color = int(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "color"))

        # then overwrite with the values of the FreeCAD object
        self._get_freecad_properties(freecad_object)

        self.sheet = sheet
    def parse_from_json(self, json_object):
        self._parse_name_and_uuid_from_json(json_object)
        self._parse_position_and_rotation_from_json(json_object)
        self.sheet = JsonSpreadSheet(self)

        # Remember if the current project has children or not
        self.has_children = len(list(json_object[JSON_ELEMNT_CHILDREN])) != 0

        return self
    def test_read_sheet_attribute(self):
        active_document = ActiveDocument(
            self._WORKING_DIRECTORY).open_set_and_get_document(
                "PartSheetTest_Read")
        json_part = AJsonPart().parse_from_json(self._json_test_object)
        json_spread_sheet = JsonSpreadSheet(json_part)

        json_spread_sheet.write_to_freecad(active_document)

        attribute = json_spread_sheet.read_sheet_attribute(
            active_document, "height")

        self.assertEquals(attribute, 300, "Got correct value")
    def create_from_freecad(cls, freecad_object, freecad_sheet):
        Log("Reading part object...\n")

        # get the shape from the sheet and not the object
        part = AJsonPart()
        sheet = JsonSpreadSheet(part)
        shape = sheet.read_sheet_attribute_from_freecad(freecad_sheet, "shape")

        create_method_name = "_create_json_part_" + shape.lower()
        create_method_dispatch = getattr(
            cls, create_method_name,
            lambda: Err("Invalid call to : " + create_method_name + "\n"))
        part_x = create_method_dispatch()

        return part_x
    def test_full_import_shape_geometry(self):
        json_importer = JsonImporter(self._WORKING_DIRECTORY)
        json_object = json.loads(TEST_JSON_FULL_GEOMETRY)

        # get the current module path and get the directory for the test resource
        # place that path into the json object before executing the transformations
        stl_test_resource_path = Environment.get_test_resource_path(
            "Switch.stl")
        stl_test_resource_path_cp = os.path.join(self._WORKING_DIRECTORY,
                                                 "Switch_cp.stl")
        copyfile(stl_test_resource_path, stl_test_resource_path_cp)
        json_object[JSON_PARTS][0][
            JSON_ELEMENT_STL_PATH] = stl_test_resource_path_cp

        _, json_product, active_document = json_importer.full_import(
            json_object)

        self.assertEqual(len(json_product.children), 1,
                         "Correct amount of children")
        self.assertEqual(len(active_document.app_active_document.RootObjects),
                         2, "Found correct amount of 1 object and 1 sheet")
        name = "Geometry_cc14e2c7_9d7e_4cf2_8d6d_9b8cf5e96d56"
        self.assertEqual(
            active_document.app_active_document.RootObjects[0].Label, name,
            "Found the right object")
        self.assertEqual(
            active_document.app_active_document.RootObjects[1].Label,
            FREECAD_PART_SHEET_NAME + "_" + name, "Found the right object")

        active_document = ActiveDocument(
            self._WORKING_DIRECTORY).open_set_and_get_document(
                PART_IDENTIFIER +
                "Geometry_38eae3a5_8338_4a51_b1df_5583058f9e77")
        self.assertEqual(len(active_document.app_active_document.RootObjects),
                         5, "Found correct amount of 4 object and 1 sheet")

        freecad_sheet = active_document.app_active_document.Objects[4]
        sheet = JsonSpreadSheet(JsonPartGeometry())
        stl_path = sheet.read_sheet_attribute_from_freecad(
            freecad_sheet, "stl_path")

        # Check that the extra attribute for the STL files got written to the sheet
        self.assertEqual(stl_path, stl_test_resource_path_cp,
                         "The path is written to the spreadsheet")
    def parse_from_json(self, json_object):
        '''
        This method parses the properties from the json object as they are needed for FreeCAD
        Transformations are also applied where needed.
        '''
        self.name = str(json_object[JSON_ELEMENT_NAME])
        self.shape = str(json_object[JSON_ELEMENT_SHAPE])
        self.uuid = str(json_object[JSON_ELEMENT_UUID]).replace("-", "_")

        # the coordinate system between virtual satellite and FreeCAD seem
        # to be identical. no Further adjustments or transformations needed.
        self.length = float(json_object[JSON_ELEMENT_LENGTH_X]) * M_TO_MM
        self.width = float(json_object[JSON_ELEMENT_LENGTH_Y]) * M_TO_MM
        self.height = float(json_object[JSON_ELEMENT_LENGTH_Z]) * M_TO_MM

        self.radius = float(json_object[JSON_ELEMENT_RADIUS]) * M_TO_MM

        # shift from pure rgb to rgba
        self.color = int(json_object[JSON_ELEMENT_COLOR]) << 8

        self.sheet = JsonSpreadSheet(self)

        return self
    def test_is_sheet_attached(self):
        active_document = ActiveDocument(
            self._WORKING_DIRECTORY).open_set_and_get_document(
                "PartSheetTest_Attached")
        json_part = AJsonPart().parse_from_json(self._json_test_object)
        json_spread_sheet = JsonSpreadSheet(json_part)

        self.assertFalse(json_spread_sheet.is_sheet_attached(active_document),
                         "There is no sheet yet")

        json_spread_sheet.write_to_freecad(active_document)

        self.assertTrue(json_spread_sheet.is_sheet_attached(active_document),
                        "Sheet got attached and can be read")
class AJsonPart():
    '''
    This class translates a json object into a more specific
    one which represents all relevant information of a part. On
    top of that this class will provide additional functionality
    such as swapping the axes if needed as well as cleaning uuid etc.
    '''
    def __init__(self):
        self.attributes = {
            "name": "-",
            "shape": "-",
            "uuid": "-",
            "length": "mm",
            "width": "mm",
            "height": "mm",
            "radius": "mm",
            "color": "rgba"
        }

    def parse_from_json(self, json_object):
        '''
        This method parses the properties from the json object as they are needed for FreeCAD
        Transformations are also applied where needed.
        '''
        self.name = str(json_object[JSON_ELEMENT_NAME])
        self.shape = str(json_object[JSON_ELEMENT_SHAPE])
        self.uuid = str(json_object[JSON_ELEMENT_UUID]).replace("-", "_")

        # the coordinate system between virtual satellite and FreeCAD seem
        # to be identical. no Further adjustments or transformations needed.
        self.length = float(json_object[JSON_ELEMENT_LENGTH_X]) * M_TO_MM
        self.width = float(json_object[JSON_ELEMENT_LENGTH_Y]) * M_TO_MM
        self.height = float(json_object[JSON_ELEMENT_LENGTH_Z]) * M_TO_MM

        self.radius = float(json_object[JSON_ELEMENT_RADIUS]) * M_TO_MM

        # shift from pure rgb to rgba
        self.color = int(json_object[JSON_ELEMENT_COLOR]) << 8

        self.sheet = JsonSpreadSheet(self)

        return self

    def parse_to_json(self):
        json_dict = {
            JSON_ELEMENT_NAME: self.name,
            JSON_ELEMENT_UUID: self.uuid.replace("_", "-"),
            JSON_ELEMENT_SHAPE: self.shape,
            JSON_ELEMENT_LENGTH_X: self.length / M_TO_MM,
            JSON_ELEMENT_LENGTH_Y: self.width / M_TO_MM,
            JSON_ELEMENT_LENGTH_Z: self.height / M_TO_MM,
            JSON_ELEMENT_RADIUS: self.radius / M_TO_MM,
            JSON_ELEMENT_COLOR: self.color >> 8
        }

        return json_dict

    def _clean_freecad_object(self, active_document):
        '''
        This method checks if the object to be created complies with the one
        mentioned in the sheet, if not it will remove the object to create space for a new one
        '''
        if self.sheet.is_sheet_attached(active_document):
            current_shape_type = self.sheet.read_sheet_attribute(
                active_document, "shape")
            # if the current shape type is different than the once specified in
            # the json, it means it has been changed and needs to be updated
            # therefore all previous objects should be removed
            if current_shape_type != self.shape:
                root_objects = list(
                    active_document.app_active_document.RootObjects)
                for root_object in root_objects:
                    active_document.app_active_document.removeObject(
                        root_object.Name)

    def _create_freecad_object(self, active_document):
        '''
        This method handles the correct creation of the FreeCAD object depending
        on the primitive or geometry as selected in the Virtual Satellite and
        the json respectively. The primitives have the same name in FreeCAD as in
        Virtual Satellite. Geometries instead need special treatment.
        '''
        object_name_and_type = self.get_shape_type()
        document_object = active_document.app_active_document.getObject(
            object_name_and_type)

        if document_object is None:
            object_type = "Part::" + object_name_and_type
            object_name = object_name_and_type
            active_document.app_active_document.addObject(
                object_type, object_name)

    def _set_freecad_name_and_color(self, active_document):
        object_name_and_type = self.get_shape_type()

        active_document.app_active_document.getObject(
            object_name_and_type).Label = self.name
        active_document.gui_active_document.getObject(
            object_name_and_type).ShapeColor = self.color

    def _set_freecad_properties(self, active_document):
        pass

    def write_to_freecad(self, active_document):
        '''
        This method uses all information from this json part to create
        the corresponding object in FreeCAD.
        '''
        # Create the FreeCAD object and set its properties
        self._clean_freecad_object(active_document)
        self._create_freecad_object(active_document)
        self._set_freecad_name_and_color(active_document)
        self._set_freecad_properties(active_document)

        # Attach the Spreadsheet with a copy of all relevant parameters
        # to the FreeCAD document
        self.sheet.write_to_freecad(active_document)

        # Recompute the object on FreeCAD side
        object_name_and_type = self.get_shape_type()
        active_document.app_active_document.getObject(
            object_name_and_type).recompute()

    def _get_freecad_properties(self, freecad_object):
        """
        Function to be overwritten by concrete part implementations
        """
        pass

    def read_from_freecad(self, freecad_object, freecad_sheet):
        sheet = JsonSpreadSheet(self)

        self.name = sheet.read_sheet_attribute_from_freecad(
            freecad_sheet, "name")
        self.shape = sheet.read_sheet_attribute_from_freecad(
            freecad_sheet, "shape")
        self.uuid = sheet.read_sheet_attribute_from_freecad(
            freecad_sheet, "uuid")

        # initialize with values of the sheet
        self.length = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "length"))
        self.width = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "width"))
        self.height = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "height"))
        self.radius = float(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "radius"))
        self.color = int(
            sheet.read_sheet_attribute_from_freecad(freecad_sheet, "color"))

        # then overwrite with the values of the FreeCAD object
        self._get_freecad_properties(freecad_object)

        self.sheet = sheet

    def get_shape_type(self):
        shape_type = self.shape.lower().capitalize()
        return shape_type

    def get_unique_name(self):
        return PART_IDENTIFIER + _get_combined_name_uuid(self.name, self.uuid)
class AJsonProduct():
    def __init__(self):
        self.attributes = {
            "name": "-",
            "uuid": "-",
            "part_name": "-",
            "part_uuid": "-",
            "pos_x": "mm",
            "pos_y": "mm",
            "pos_z": "mm",
            "rot_x": "°",
            "rot_y": "°",
            "rot_z": "°",
        }

        self.pos_x = 0.0
        self.pos_y = 0.0
        self.pos_z = 0.0

        self.rot_x = 0.0
        self.rot_y = 0.0
        self.rot_z = 0.0

        self.name = None
        self.uuid = None

    def _parse_name_and_uuid_from_json(self, json_object):
        self.name = str(json_object[JSON_ELEMENT_NAME])
        self.uuid = str(json_object[JSON_ELEMENT_UUID]).replace("-", "_")

        json_has_part_uuid = JSON_ELEMENT_PART_UUID in json_object
        json_has_part_name = JSON_ELEMENT_PART_NAME in json_object

        if json_has_part_name and json_has_part_uuid:
            self.part_uuid = str(json_object[JSON_ELEMENT_PART_UUID]).replace(
                "-", "_")
            self.part_name = str(json_object[JSON_ELEMENT_PART_NAME]).replace(
                "-", "_")

    def _parse_position_and_rotation_from_json(self, json_object):
        # the coordinate system between virtual satellite and FreeCAD seem
        # to be identical. no Further adjustments or transformations needed.
        self.pos_x = float(json_object[JSON_ELEMENT_POS_X]) * M_TO_MM
        self.pos_y = float(json_object[JSON_ELEMENT_POS_Y]) * M_TO_MM
        self.pos_z = float(json_object[JSON_ELEMENT_POS_Z]) * M_TO_MM

        # the coordinate system between virtual satellite and FreeCAD seem
        # to be identical. no Further adjustments or transformations needed.
        self.rot_x = float(json_object[JSON_ELEMENT_ROT_X]) * RAD_TO_DEG
        self.rot_y = float(json_object[JSON_ELEMENT_ROT_Y]) * RAD_TO_DEG
        self.rot_z = float(json_object[JSON_ELEMENT_ROT_Z]) * RAD_TO_DEG

    def parse_from_json(self, json_object):
        self._parse_name_and_uuid_from_json(json_object)
        self._parse_position_and_rotation_from_json(json_object)
        self.sheet = JsonSpreadSheet(self)

        # Remember if the current project has children or not
        self.has_children = len(list(json_object[JSON_ELEMNT_CHILDREN])) != 0

        return self

    def parse_to_json(self):
        json_dict = {
            JSON_ELEMENT_NAME: self.name.replace("_", "-"),
            JSON_ELEMENT_UUID: self.uuid.replace("_", "-"),
            JSON_ELEMENT_POS_X: self.pos_x / M_TO_MM,
            JSON_ELEMENT_POS_Y: self.pos_y / M_TO_MM,
            JSON_ELEMENT_POS_Z: self.pos_z / M_TO_MM,
            JSON_ELEMENT_ROT_X: self.rot_x / RAD_TO_DEG,
            JSON_ELEMENT_ROT_Y: self.rot_y / RAD_TO_DEG,
            JSON_ELEMENT_ROT_Z: self.rot_z / RAD_TO_DEG
        }

        if self.is_part_reference():
            json_dict[JSON_ELEMENT_PART_UUID] = self.part_uuid.replace(
                "_", "-")
            json_dict[JSON_ELEMENT_PART_NAME] = self.part_name.replace(
                "_", "-")

        # will be overwritten from ProductAssembly
        json_dict[JSON_ELEMNT_CHILDREN] = []

        return json_dict

    def _create_freecad_part(self, active_document):
        '''
        This method imports the part referenced by the product.
        The referenced part will be placed under the product part name into
        the assembly. E.g. A BasePlate will be added as BasePlateBottom to the
        assembly.
        '''
        import_part_file_name = self.get_part_unique_name()
        import_part_name_in_product = self.get_unique_name()
        import_part_full_path = active_document.get_file_full_path(
            import_part_file_name)

        imported_product_part = importPartFromFile(
            active_document.app_active_document, import_part_full_path)
        imported_product_part.Label = import_part_name_in_product

    def _set_freecad_position_and_rotation(self, active_document):
        product_part_name = self.get_unique_name()

        product_part = active_document.app_active_document.getObjectsByLabel(
            product_part_name)[0]

        # First translate than rotate around X, Y and Z
        vector_translation = active_document.app.Vector(
            self.pos_x, self.pos_y, self.pos_z)
        vector_rotation_zero = active_document.app.Rotation(VECTOR_ZERO, 0)
        vector_rotation_x = active_document.app.Rotation(VECTOR_X, self.rot_x)
        vector_rotation_y = active_document.app.Rotation(VECTOR_Y, self.rot_y)
        vector_rotation_z = active_document.app.Rotation(VECTOR_Z, self.rot_z)

        placement = product_part.Placement  # Placement()

        placement_translation = active_document.app.Placement(
            vector_translation, vector_rotation_zero, VECTOR_ZERO)

        placement_rotation_x = active_document.app.Placement(
            VECTOR_ZERO, vector_rotation_x, VECTOR_ZERO)

        placement_rotation_y = active_document.app.Placement(
            VECTOR_ZERO, vector_rotation_y, VECTOR_ZERO)

        placement_rotation_z = active_document.app.Placement(
            VECTOR_ZERO, vector_rotation_z, VECTOR_ZERO)

        placement = placement_rotation_x.multiply(placement)
        placement = placement_rotation_y.multiply(placement)
        placement = placement_rotation_z.multiply(placement)
        placement = placement_translation.multiply(placement)

        product_part.Placement = placement

    def _reset_freecad_position_and_rotation(self, active_document):
        product_part_name = self.get_unique_name()

        product_part = active_document.app_active_document.getObjectsByLabel(
            product_part_name)[0]
        product_part.Placement = FreeCAD.Placement()

    def _write_freecad_part(self, active_document):
        self._create_freecad_part(active_document)
        self._set_freecad_position_and_rotation(active_document)

    def _update_freecad_part(self, active_document):
        self._reset_freecad_position_and_rotation(active_document)
        self._set_freecad_position_and_rotation(active_document)

    def write_to_freecad(self, active_document, create=True):

        if (create):
            self._write_freecad_part(active_document)
        # only update the existing part
        else:
            self._update_freecad_part(active_document)
            # remove the existing sheet
            active_document.app_active_document.removeObject(
                self.sheet.create_sheet_name())

        self.sheet.write_to_freecad(active_document)

    def _get_freecad_rotation(self, freecad_object):
        # reverse rotation

        rot = freecad_object.Placement.Rotation.toEuler()

        self.rot_z = rot[0]
        self.rot_y = rot[1]
        self.rot_x = rot[2]

    def read_from_freecad(self,
                          active_document,
                          working_output_directory,
                          part_list,
                          freecad_object=None,
                          freecad_sheet=None):

        if (freecad_sheet is not None):
            sheet = JsonSpreadSheet(self)
            self.name = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "name")
            self.uuid = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "uuid")
            self.part_name = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "part_name")
            self.part_uuid = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "part_uuid")
        # get properties from name, because a root assembly has no sheet
        else:
            # document_name is identifier_name_uuid
            document_name = active_document.app_active_document.Name
            self.name = document_name.split("_")[1]
            self.uuid = "_".join(document_name.split("_")[2:])

        if (freecad_object is not None):
            pos = freecad_object.Placement.Base

            self.pos_x = pos[0]
            self.pos_y = pos[1]
            self.pos_z = pos[2]

            self._get_freecad_rotation(freecad_object)

            child_cnt = 0
            for obj in active_document.app_active_document.Objects:
                name = obj.Name

                if (FREECAD_PART_SHEET_NAME in name):
                    child_cnt += 1
                elif (PRODUCT_IDENTIFIER in name or PART_IDENTIFIER in name):
                    child_cnt += 1

            self.has_children = child_cnt

        if (self.is_part_reference()):
            # read in the referenced part (if not read in already)

            part_name = self.get_part_unique_name()

            # only have a part one time in the list
            if (part_name not in [item[0] for item in part_list]):
                part_document = ActiveDocument(
                    working_output_directory).open_set_and_get_document(
                        part_name)
                for obj in part_document.app_active_document.Objects:
                    if (obj.Label == self.part_name):
                        part_object = obj
                    elif (FREECAD_PART_SHEET_NAME in obj.Label):
                        part_sheet = obj
                factory = JsonPartFactory()
                part = factory.create_from_freecad(part_object, part_sheet)
                part.read_from_freecad(part_object, part_sheet)
                part_list.append((part_name, part))

    def get_unique_name(self):
        '''
        Returns the unique name of the current product
        '''
        return _get_combined_name_uuid(self.name, self.uuid)

    def get_part_unique_name(self):
        '''
        Returns the unique name of the referenced part
        '''
        return PART_IDENTIFIER + _get_combined_name_uuid(
            self.part_name, self.part_uuid)

    def is_part_reference(self):
        '''
        This method checks for the existence of the properties partUuid and partName.
        In case they are both present, the current product directly references a apart
        '''
        has_part_uuid = hasattr(self, "part_uuid")
        has_part_name = hasattr(self, "part_name")

        return has_part_uuid and has_part_name

    def has_equal_values(self, other):
        """
        Compares values with another AJsonProduct
        """
        if (isinstance(other, AJsonProduct)):
            return (self.pos_x == other.pos_x and self.pos_y == other.pos_y
                    and self.pos_z == other.pos_z and self.rot_x == other.rot_x
                    and self.rot_y == other.rot_y and self.rot_z == other.rot_z
                    and self.name == other.name and self.uuid == other.uuid)

        return NotImplemented
    def read_from_freecad(self,
                          active_document,
                          working_output_directory,
                          part_list,
                          freecad_object=None,
                          freecad_sheet=None):

        if (freecad_sheet is not None):
            sheet = JsonSpreadSheet(self)
            self.name = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "name")
            self.uuid = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "uuid")
            self.part_name = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "part_name")
            self.part_uuid = sheet.read_sheet_attribute_from_freecad(
                freecad_sheet, "part_uuid")
        # get properties from name, because a root assembly has no sheet
        else:
            # document_name is identifier_name_uuid
            document_name = active_document.app_active_document.Name
            self.name = document_name.split("_")[1]
            self.uuid = "_".join(document_name.split("_")[2:])

        if (freecad_object is not None):
            pos = freecad_object.Placement.Base

            self.pos_x = pos[0]
            self.pos_y = pos[1]
            self.pos_z = pos[2]

            self._get_freecad_rotation(freecad_object)

            child_cnt = 0
            for obj in active_document.app_active_document.Objects:
                name = obj.Name

                if (FREECAD_PART_SHEET_NAME in name):
                    child_cnt += 1
                elif (PRODUCT_IDENTIFIER in name or PART_IDENTIFIER in name):
                    child_cnt += 1

            self.has_children = child_cnt

        if (self.is_part_reference()):
            # read in the referenced part (if not read in already)

            part_name = self.get_part_unique_name()

            # only have a part one time in the list
            if (part_name not in [item[0] for item in part_list]):
                part_document = ActiveDocument(
                    working_output_directory).open_set_and_get_document(
                        part_name)
                for obj in part_document.app_active_document.Objects:
                    if (obj.Label == self.part_name):
                        part_object = obj
                    elif (FREECAD_PART_SHEET_NAME in obj.Label):
                        part_sheet = obj
                factory = JsonPartFactory()
                part = factory.create_from_freecad(part_object, part_sheet)
                part.read_from_freecad(part_object, part_sheet)
                part_list.append((part_name, part))