ediloren / EM-Workbench-for-FreeCAD

This project is dedicated to building an ElectroMagnetic workbench for FreeCAD. FreeCAD is a free 3D parametric CAD. FreeCAD is used as pre-processor interfacing to the open source electromagnetic field solvers FastHenry and FasterCap.
GNU Lesser General Public License v2.1
56 stars 16 forks source link

issues with sketches, modeling bond wires #14

Open g3grau opened 2 weeks ago

g3grau commented 2 weeks ago

Hi,

I just tried to use the EM WB to model package parasitics in a QFN package (in 0.21.2). Is there already a way to generate FastCap files?

a) Initially I tried to create datum planes in which I created a path for the bond wire. For different wires (at different angle) I need different planes. The path coordinates in each sketch are very similar, but the translation into absolute coordinates is not. When selecting a sketch polyline to create a path (chain of segments), the exported coordinates are the local coordinates within the sketch which results in wrong results.

b) A problem with this approach is that the angle of the bond wire and that of the package lead are different. I tried to add a few extra points outside of the sketch and connect them to the sketch, but 1. there are no visible/selectable points if I created the path from a sketch and 2. duplicating sketch end point and package start point and joining them with "equivalent" suffers from the same coordinate problem. Finally, also creating a vector suffers from that.

c) Another confusing thing is that for a sketch polyline with 5 points I get FHNode0005 to 0009 from the FHPath command. I assume that the order of points in the tree is following the polyline points. I would assume that 005 is the start and 009 is the end. However, it starts with 006 and increases to 009 and ends with 005. I'm connecting the topmost and bottom-most points to create a vector and the result is correct, thus it's just a non-intuitive numbering.

d) While experimenting and "saving as" I also found that I cannot export to fastHenry from the reopened model, something is broken. The same thing occurs from saving and reopening the file. The easy fix is to delete the FHSolver node and create a new one.

I created a macro which solves the coordinate entrance and angle change issues without placing points. It still needs a bit of extra work to generate the FHPath and FHPort nodes. In addition, I want the geometry of the last segment to be different than the others. I assume that this requires a separate wire and maybe an equivalent node (how to know the node in advance?). Any suggestion how that can be improved?

Chip_in_QFN48_7x7.flat.FCStd.gz


"""
8.11.2024 GG: FreeCAD Macro for EM Workbench
Purpose is creation of bond wires and inductance paths for FastHenry/FasterCap

Issues: Creation of single "wire" and FHPath does not allow per segment w/h
"""
import FreeCAD as App
import Draft
import math
from PySide import QtGui, QtCore

# Define a function to create and show the dialog box
class BondWireDialog(QtGui.QDialog):
    def __init__(self):
        super(BondWireDialog, self).__init__()

        # Set up the layout
        layout = QtGui.QFormLayout(self)
        self.comment = QtGui.QLabel("Specify the parameters for bond wire generation (in mm!!) below:")
        layout.addRow(self.comment)

        self.comment = QtGui.QLabel("Number of pins:")
        layout.addRow(self.comment)
        self.N = QtGui.QSpinBox()
        self.N.setValue(14)

        # Add input fields for origins and deltas
        self.comment = QtGui.QLabel("Start of bond wire:")
        layout.addRow(self.comment)
        self.x0_pad = QtGui.QLineEdit("700.0e-3")   # start of bond wire
        self.y0_pad = QtGui.QLineEdit("100.0e-3")
        self.z0_pad = QtGui.QLineEdit("214.0e-3")

        self.comment = QtGui.QLabel("End of bond wire (inner package pad):")
        layout.addRow(self.comment)
        self.x0_pin = QtGui.QLineEdit("-40.0e-3")   # end of bond wire
        self.y0_pin = QtGui.QLineEdit("-600.0e-3")
        self.z0_pin = QtGui.QLineEdit("0.0")

        self.comment = QtGui.QLabel("Solder location (outer package pad):")
        layout.addRow(self.comment)
        self.x1_pin = QtGui.QLineEdit("-330.0e-3")   # solder location
        self.y1_pin = QtGui.QLineEdit("-1200.0e-3")
        self.z1_pin = QtGui.QLineEdit("-200.0e-3")

        self.comment = QtGui.QLabel("Pitch (bondpad, inner pad ring, outer pad ring):")
        layout.addRow(self.comment)
        self.dx_pad = QtGui.QLineEdit("250e-3")
        self.dy_pad = QtGui.QLineEdit("0.0")

        self.dx1_pin = QtGui.QLineEdit("360.0e-3")  # package pitch on die side 
        self.dy1_pin = QtGui.QLineEdit("0.0")

        self.dx2_pin = QtGui.QLineEdit("400.0e-3")  # package pitch on solder side
        self.dy2_pin = QtGui.QLineEdit("0.0")

        self.comment = QtGui.QLabel("Exit angle(°), length of rising part, length of flat part:")
        layout.addRow(self.comment)
        self.alpha = QtGui.QLineEdit("30.0")
        self.l1 = QtGui.QLineEdit("200.0e-3")
        self.l2 = QtGui.QLineEdit("100.0e-3")

        # Add widgets to the layout
        layout.addRow("Number of Wires (N):", self.N)

        layout.addRow("Pad Origin X:", self.x0_pad)
        layout.addRow("Pad Origin Y:", self.y0_pad)
        layout.addRow("Pad Origin Z:", self.z0_pad)
        layout.addRow("Pin Origin X (inner):", self.x0_pin)
        layout.addRow("Pin Origin Y (inner):", self.y0_pin)
        layout.addRow("Pin Origin Z (inner):", self.z0_pin)
        layout.addRow("Pin Origin X (outer):", self.x1_pin)
        layout.addRow("Pin Origin Y (outer):", self.y1_pin)
        layout.addRow("Pin Origin Z (outer):", self.z1_pin)

        layout.addRow("Delta Pad X:", self.dx_pad)
        layout.addRow("Delta Pad Y:", self.dy_pad)
        layout.addRow("Delta Pin X (inner):", self.dx1_pin)
        layout.addRow("Delta Pin Y (inner):", self.dy1_pin)
        layout.addRow("Delta Pin X (outer):", self.dx2_pin)
        layout.addRow("Delta Pin Y (outer):", self.dy2_pin)

        layout.addRow("Wire exit angle (alpha in °):", self.alpha)
        layout.addRow("Wire segment length up (l1):", self.l1)
        layout.addRow("Wire segment length flat (l2):", self.l2)

        # Add OK and Cancel buttons
        self.buttons = QtGui.QDialogButtonBox(
            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, self)
        layout.addWidget(self.buttons)

        # Connect the buttons to dialog functions
        self.buttons.accepted.connect(self.accept)
        self.buttons.rejected.connect(self.reject)

    # Function to retrieve input values
    def get_values(self):
        return {
            'x0_pad': float(self.x0_pad.text()),
            'y0_pad': float(self.y0_pad.text()),
            'z0_pad': float(self.z0_pad.text()),
            'x0_pin': float(self.x0_pin.text()),
            'y0_pin': float(self.y0_pin.text()),
            'z0_pin': float(self.z0_pin.text()),
            'x1_pin': float(self.x1_pin.text()),
            'y1_pin': float(self.y1_pin.text()),
            'z1_pin': float(self.z1_pin.text()),
            'dx_pad': float(self.dx_pad.text()),
            'dy_pad': float(self.dy_pad.text()),
            'dx1_pin': float(self.dx1_pin.text()),
            'dy1_pin': float(self.dy1_pin.text()),
            'dx2_pin': float(self.dx2_pin.text()),
            'dy2_pin': float(self.dy2_pin.text()),
            'N': self.N.value(),
            'alpha': float(self.alpha.text()),
            'l1': float(self.l1.text()),
            'l2': float(self.l2.text()),
        }

# Function to generate intermediate points
def calculate_points(start, middle, end):
    x0, y0, z0 = start
    x1, y1 = middle
    points = [start]
    alpha = math.radians(values['alpha'])
    beta1 = math.atan2(y1-y0,x1-x0)
    dx = values['l1'] * math.cos(alpha) * math.cos(beta1)
    dy = values['l1'] * math.cos(alpha) * math.sin(beta1)
    dz = values['l1'] * math.sin(alpha)
    points.append((x0 + dx, y0 + dy, z0 + dz))
    points.append((x0 + dx + values['l2'] * math.cos(beta1), y0 + dy + values['l2'] * math.sin(beta1), z0 + dz))
    points.append(middle)   # end of bond wire
    #beta2 = math.atan2(y2-y1,x2-x1)
    points.append(end)
    print(f"Path {points}")
    return points

# Show the dialog and get values
dialog = BondWireDialog()
if dialog.exec_() == QtGui.QDialog.Accepted:
    values = dialog.get_values()
    # Use the values from the dialog
    start_points = [(values['x0_pad'] + i * values['dx_pad'], 
                     values['y0_pad'] + i * values['dy_pad'], 
                     values['z0_pad']) 
                    for i in range(values['N'])]
    mid_points = [(values['x0_pin'] + i * values['dx1_pin'], 
                   values['y0_pin'] + i * values['dy1_pin'], 
                   values['z0_pin']) 
                  for i in range(values['N'])]
    end_points = [(values['x1_pin'] + i * values['dx2_pin'], 
                   values['y1_pin'] + i * values['dy2_pin'], 
                   values['z1_pin']) 
                  for i in range(values['N'])]
    # Create polylines
    doc = App.ActiveDocument
    for i in range(values['N']):
        start = start_points[i]
        middle = mid_points[i]
        end = end_points[i]        
        points = calculate_points(start, middle, end)
        fc_points = [App.Vector(p) for p in points]
        print(f"Drawing wire {fc_points}")
        Draft.makeWire(fc_points, closed=False)
    doc.recompute()
samerps commented 2 weeks ago

hi, you might want to have a look at https://github.com/samerps/Blender-FastHenry/

I just released this addon for Blender, here is a video showing it calculating the impedance of wire bonds and lead frame of a TO-220 package.