Exemple #1
0
 def create_basic_pcb(self):
     pcb = meta.Part("PCB")
     pcb.create_blank()
     pcb.fc.Shape = meta.make_box(self.width, self.height, self.boardthick)
     pcb.fc.ViewObject.ShapeColor = PCBColors.BOARD
     logger.info('Board dimensions: h={0}  w={1} lay={2}'.format(
         self.height, self.width, self.layers))
Exemple #2
0
    def board_border(self, border):
        """
            Generate the base structure of the PCB.
        """
        pcb = meta.Sketch("PCB_Border")
        pcb.create_blank()
        pcb.set_sketch_plane('XY', 0.0)
        pcb.sketch_from_coords(border)
        pad = pcb.pad_sketch(self.boardthick, 0, 0, 0.0, 0, None)

        pcb_part = meta.Part("PCB")
        pcb_part.create_blank("part")
        meta.copy_shape(pad, pcb_part, True)

        color_array = []
        for i in range(len(pcb_part.fc.Shape.Faces)):
            color_array.append(PCBColors.BOARD)
        pcb_part.set_colors(color_array)
Exemple #3
0
def generate_assembly(doc, component_dict):

    [layout, CT] = read_input_files(component_dict,
                                    os.path.join(os.getcwd(), 'layout.json'))

    # The component assembly of a subcircuit will have an entry in layout.json. We want to disregard these entries,
    #    as the components that make up the subcircuit will be added individually later on.
    no_relative_id_pkgs = [
        p for p in layout.packages if p.relComponentId is None
    ]
    subcircuit_pkgs = []
    for p in layout.packages:
        if p.relComponentId is not None:
            for s in no_relative_id_pkgs:
                if p.relComponentId == s.guid:
                    subcircuit_pkgs.append(s.guid)
    subcircuit_pkgs = list(set(subcircuit_pkgs))

    meta.new_document(doc)

    # Create PCB board
    boardthick = 0.6
    chipThick = 0.3
    create_pcb(layout, boardthick)

    # Initialize chip counter
    topchips = 0
    bottomchips = 0
    placeholders = 0

    imported_cad = {}
    for p in sorted(layout.packages,
                    reverse=True,
                    key=lambda p: p.width * p.height):
        if p.guid in subcircuit_pkgs:
            continue

        placeholder = False  # Start by assuming component exists, try to prove otherwise.
        component = p.guid

        logger.debug('\n\n')
        logger.debug('-- COMPONENT --')
        logger.debug('Component Key: ' + component)

        if component not in CT:
            logger.warning(
                'Component {0} not found in CT.json, creating placeholder...'.
                format(component))
            placeholder = True

        ######################################
        ''' PLACEMENT CALCULATION '''

        # Check for relative placement
        xc = None
        yc = None
        relative_package = None
        if p.relComponentId is not None:
            logger.info("Relative constraints found!")

            relative_package = [
                pkg for pkg in layout.packages if pkg.guid == p.relComponentId
            ][0]
            logger.info('Package relative to: ' + str(relative_package.guid))

            # Need to move the lower left-hand point for rotated reference package
            [xc, yc] = geom.rotate_and_translate(relative_package, p.x, p.y)

            # Cumulative rotation between package and its reference
            rotate = p.rotation + relative_package.rotation
            do_rotate = 1 if rotate == 1 or rotate == 3 or rotate == 5 else 0

            # xc/yc are coordinates of moved LLH point - need to move this so it is back to LLH
            #   --> EG, LLH rotated 180 degrees results in point at URH
            if relative_package.rotation == 1:
                xc -= p.width if not do_rotate else p.height
            elif relative_package.rotation == 2:
                xc -= p.width if not do_rotate else p.height
                yc -= p.height if not do_rotate else p.width
            elif relative_package.rotation == 3:
                yc -= p.height if not do_rotate else p.width
        else:
            logger.info("No constraints present. Using regular X/Y values.")
            xc = p.x
            yc = p.y
            rotate = p.rotation
            do_rotate = 1 if rotate == 1 or rotate == 3 else 0

        item = None
        cad = None
        if not placeholder:
            item = CT[component]
            cad = CT[component].get("cadpath")

        [scale, rotation, translation,
         placeholder] = get_cad_srt(cad, item, placeholder)
        trans = meta.transform_cad_to_eda(
            rotation, [t * s for t, s in zip(translation, scale)])

        # Define placement variables based on top/bottom layer that the component instance is on
        if p.layer == 0:  # Component is on top layer of PCB
            Z = boardthick  # Component height on board (Top/Bottom of PCB)
            placeholder_offset = 0.0
            alpha = 90.0  # 90.0 degree rotation
            board_orient = [0, 0, rotate * alpha
                            ]  # Component orientation relative to board
            topchips += 1
        elif p.layer == 1:  # Component is on bottom layer of PCB
            Z = 0.0
            placeholder_offset = chipThick
            alpha = -90.0
            trans[2] *= -1  # Flip to handle 'pushing down' the component.
            # Two-way 180 degree flips accounts for switch to bottom
            board_orient = [0, 180, 180 + rotate * alpha]
            bottomchips += 1
        else:
            exitwitherror(
                'Invalid layer specified for package {0}'.format(component))

        rot = [sum(x) for x in zip(rotation, board_orient)]

        # Component center point. If component is rotated width and height are reversed.
        width = ((1 - do_rotate) * p.width + do_rotate * p.height)
        height = ((1 - do_rotate) * p.height + do_rotate * p.width)

        [originx, originy] = geom.rotate_vector(p.originx, p.originy, rotate)
        X = xc + 0.5 * width - originx
        Y = yc + 0.5 * height - originy

        ######################################
        ''' FREECAD OBJECT CREATION '''

        if not placeholder:
            if cad not in imported_cad:
                solid = meta.Part(component)  # Instantiate new part
                """ Query number of parts before and after import to determine if fuse needs to occur.
                    Some single component may import each SHAPE_REPRESENTATION as its own component.
                    If this happens, solidify these components to get back to one object.
                """
                num_objects_b = len(meta.get_all_objects())
                (solid.cadpath, solid.format,
                 newdoc) = meta.import_cad(cad, doc)
                logger.info(doc)
                logger.info(newdoc)
                logger.info(solid.cadpath)
                logger.info(solid.name)
                num_objects_a = len(meta.get_all_objects())

                if (num_objects_a - num_objects_b) > 1:
                    # Only ever true for STEP files - STL files have no hierarchy concept.
                    objects = [
                        meta.get_all_objects()[x]
                        for x in range(num_objects_b, num_objects_a)
                    ]
                    solid.fc = meta.fuse_components(objects, solid.name)
                else:
                    solid.fc = meta.get_active_object()

                # Store object data to avoid having to import a part multiple times (costly for STEP)
                imported_cad[cad] = {
                    'component': solid.name,
                    'ext': solid.format,
                    'cadpath': solid.cadpath,
                    'geometry': solid.get_shape(solid.format),
                    'color': solid.get_colors()
                }
            else:
                # Component has already been imported, grab data from dictionary and create new FC object.
                compdef = imported_cad[cad]
                solid = meta.Part(compdef['component'], compdef['ext'],
                                  compdef['cadpath'])
                if compdef['ext'] == 'stl':
                    solid.create_blank('mesh')
                else:
                    solid.create_blank('part')
                solid.set_shape(compdef['geometry'], solid.format)
                solid.set_colors(compdef['color'])

            logger.info("Generating " + component + " object...")
        else:
            placeholders += 1
            logger.warning(
                "Making placeholder (CAD file not found for component {0}!): ".
                format(component))
            solid = meta.Part(component)
            solid.create_blank()
            if do_rotate:
                solid.set_shape(
                    meta.make_box(p.width, p.height, chipThick, -0.5 * height,
                                  -0.5 * width))
            else:
                solid.set_shape(
                    meta.make_box(p.width, p.height, chipThick, -0.5 * width,
                                  -0.5 * height))
            solid.set_color(brd.PCBColors.PLACEHOLDER)  # Red placeholder color

        # Scale/Translate/Rotate component about X,Y,Z axes
        if not placeholder:
            # If scale vector is all ones, no need to scale to identity. This operation is costly.
            if not all([1 if float(s) == 1.0 else 0 for s in scale]):
                solid.scale(scale, False)

            solid.transform([sum(x) for x in zip([X, Y, Z], trans)],
                            rot)  # Translate & Rotate
        else:
            solid.transform(
                [X, Y, Z - placeholder_offset],
                [0.0, 0.0, rotate * alpha])  # Move component to center origin

        logger.debug('Package LLH <X,Y>: <{0},{1}>'.format(str(xc), str(yc)))
        logger.debug('Component <Width,Height>: <{0},{1}>'.format(
            str(p.width), str(p.height)))
        logger.debug('Global <X,Y,Z>: <{0},{1},{2}>'.format(
            str(X), str(Y), str(Z)))
        logger.debug('Translate: ' + str(translation))
        logger.debug('EDARotation: ' + str(rotation))
        logger.debug('Rotation: ' + str(rotate))
        logger.debug('CAD2EDATranslate: ' + str(trans))
        logger.info('Adding FreeCAD object {0} for component {1}.'.format(
            solid.name, component))

    return topchips, bottomchips, placeholders
Exemple #4
0
def replace(aradata):
    module_file = aradata["template_cadpath"]
    check_file_exists(module_file)
    logger.info("Ara template module: {0}".format(module_file))

    # Create new Document and Assembly object to store template module information.
    module = meta.Assembly(module_file.rsplit('.')[0])
    module.doc = meta.Document("AraModule")
    module.doc.new()
    (module.cadpath, module.format) = module.doc.load(module_file)

    # Loop through assembly and create a Part object for each element. Add these parts to the list of objects in doc.
    module.get_parts()
    module.doc.objects = module.parts

    # We are only interested in replacing parts that are not stock components.
    for rep_part in [
            p for p in aradata.items()
            if p[1] != "stock" and p[0] != "template_cadpath"
    ]:
        assembly_file = rep_part[1]  # Path to component
        module_label = rep_part[
            0]  # FreeCAD internal label of component in Ara template file.
        check_file_exists(assembly_file)
        logger.info("Replacing part {0}...".format(assembly_file))
        logger.info("FreeCAD part label: {0}".format(module_label))

        # Create new Document and Part/Assembly object for the replacing part we are about to load.
        if module_label == "PCB":
            replace_obj = meta.Assembly(module_label)
        else:
            replace_obj = meta.Part(module_label)
        replace_obj.doc = meta.Document("ReplacingObject")
        replace_obj.doc.new()
        try:
            (replace_obj.cadpath,
             replace_obj.format) = replace_obj.doc.load(assembly_file)
        except IOError:
            # Already checked for existence, so it means file is already open. Grab by label.
            logger.debug(
                "FreeCAD file {0} already open! Grabbing existing document.".
                format(assembly_file))

        # Grab object in module that we wish to replace, along with its bounding box in its assembled position.
        module_prt = module.grab_object_by_name(module_label)
        module_prt_bb = module_prt.get_bounding_box()

        # The only assembly that should be replaced is that of the PCB.
        if replace_obj.of_type() == "assembly":
            # Grab all objects in the assembly that will need to be translated.
            replace_obj.get_parts()
            replace_obj.doc.objects = replace_obj.parts
            replace_prt = replace_obj.grab_object_by_name(
                "PCB")  # This is the element whose bounding box we want.
        else:
            # Otherwise, the document only has the one part that will be copied.
            replace_obj.doc.objects.append(replace_obj)
            replace_obj.set_object(meta.get_active_object(replace_obj.doc))
            replace_prt = replace_obj

        replace_prt_bb = replace_prt.get_bounding_box()

        # Grab list of parts to copy into module doc, provided it is a part or mesh part (no sketches).
        objs = [
            x for x in replace_obj.doc.objects
            if (x.parent() == "Part::Feature" or x.parent() == "Mesh::Feature")
        ]

        module_objs_before = len(module.doc.objects)
        for obj in objs:
            new_obj = module.doc.copy_object(
                obj)  # Copy object from replace doc to module doc
            new_obj.set_colors(obj.get_colors(
            ))  # Color is lost in copying, so add color back to object.

        # Now copied objects are in module doc, but at the coordinates as they appear in replace doc.
        module_objs = module.doc.get_objects(module_objs_before,
                                             len(module.doc.objects))
        for obj in module_objs:
            logger.info("Translating object: {0}".format(obj.name))
            logger.debug("starting placement: {0}".format(obj.fc.Placement))
            logger.debug("deltaX: {0}".format(
                str(module_prt_bb.XMin - replace_prt_bb.XMin)))
            logger.debug("deltaY: {0}".format(
                str(module_prt_bb.YMin - replace_prt_bb.YMin)))
            logger.debug("deltaZ: {0}".format(
                str(module_prt_bb.ZMin - replace_prt_bb.ZMin)))
            logger.debug("ending placement: {0}".format(obj.fc.Placement))
            obj.translate([
                module_prt_bb.XMin - replace_prt_bb.XMin,
                module_prt_bb.YMin - replace_prt_bb.YMin,
                module_prt_bb.ZMin - replace_prt_bb.ZMin
            ])

        # Update assembly list, as parts have changed.
        module_prt.delete()
        module.parts = []
        module.get_parts()
        module.doc.objects = module.parts

        # Close current "replace" part and delete it's corresponding objects.
        replace_obj.doc.close()
        del replace_obj
        del replace_prt

    # Export to FreeCAD and STEP/STL
    meta.set_active_doc(module.doc)
    meta.export("FC", [])
    if len([x
            for x in module.doc.objects if x.parent() == "Mesh::Feature"]) > 0:
        meta.convert_parts_to_meshes(meta.get_all_objects(), logger.name)
        meta.export("STL", meta.get_all_objects())
    else:
        meta.fuse_components(meta.get_all_objects(),
                             module.doc.Name)  # For cad.js
        meta.export("STEP", [meta.get_active_object()])