Suggestions for multi layer chip CAD generation #1698
-
Hi Folks, cadquery is a great tool and I started using it very recently. My ultimate goal is to be able to import a chip layout file typically a gds (similar to dxf in my head) and convert it to a STEP file with full topological detail. As of now I have implemented a gds import and I'm able to extrude out polygons from different layers to specified thicknesses. My next step would be to implement the extrude over non-flat topology. For example, one can imagine a semiconductor chip with a etched pattern and then a thin layer of metal gets deposited. The layer has to conform to the topology of the substrate. Then another layer will be deposited which has to conform to both the substrate topology and the new layer that has been deposited. To illustrate the example, I have attached an image. This chip layout has four layers:
I'm not sure what's the best way to implement such an operation which will extrude a polygon over a topology. Any advice appreciated! |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 8 replies
-
@pruj-speed I'm assuming since you said "conform to the topology", that the substrate has a 3D curvature to it and is not planar. Potentially related issue: link Potentially related CQ feature: link The other core devs will probably have ideas as well. |
Beta Was this translation helpful? Give feedback.
-
I wonder if some inspiration can be taken from here #1193 |
Beta Was this translation helpful? Give feedback.
-
If it's possible to project a shadow of a polygon on the topology of the solid, then in principle I should be able to thicken that shadow in one direction to generate the conformal layer. I found a similar request from another user #1674. Is there an imprint method to project a wire on a solid? Looks like something like this was discussed here. Not sure if this functionality is available for a part. #1353 |
Beta Was this translation helpful? Give feedback.
-
Now able to do one layer coating. Last step is multiple layers. from cadquery.occ_impl.shapes import polyline, face, extrude, Compound
from cadquery import Vector
from cadquery.vis import show
import cadquery as cq
# Define the points of the polygon
points = [
(-1.5, -1.5),
(1.5, -1.5),
(1.5, 1.5),
(-1.5, 1.5)
]
# Step 1: Create the base cube
base_model = cq.Workplane("XY").box(10, 10, 10, centered=True)
# Step 2: Create a blind cut on the top face of the cube
base_model = base_model.faces(">>Z").workplane().rect(1, 7).cutBlind(-1)
# show(base_model)
# Step 3: Create a closed wire using the polyline function
polygon_wire = polyline(*points).close()
# Step 4: Convert the wire to a face and move it to the top of the cube
polygon_face = face(polygon_wire).moved(cq.Location(Vector(0, 0, 5)))
# Step 5: Extrude the polygon face downward to create a splitting solid
splitting_solid = extrude(polygon_face, (0, 0, -5))
# Step 6: Perform the split operation using the splitting solid
split_result = base_model.split(splitting_solid)
# Step 7: Find the intersection between the base model and the splitting solid
intersected_shape = base_model.intersect(splitting_solid)
# Step 8: Get all faces from the intersection
intersected_faces = intersected_shape.faces()
# Step 9: Filter faces located above z = 5
faces_above_z5 = []
for f in intersected_faces:
center = f.Center()
if center.z > 2.5:
faces_above_z5.append(f)
# Ensure we have faces to work with
if not faces_above_z5:
raise ValueError("No faces found above z = 2.5")
# Step 10: Fuse all relevant faces into a single face to handle overlaps
# Initialize the fused shape with the first face
fused_shape = faces_above_z5[0]
# Iterate through the remaining faces and fuse them
for face_to_fuse in faces_above_z5[1:]:
fused_shape = fused_shape.fuse(face_to_fuse)
# Wrap the fused shape into a CadQuery Face object
fused_face = cq.Face(fused_shape.wrapped)
# show(fused_face)
# Step 11: Thicken the fused face upwards to create a new solid
# Note: 'thicken' thickens in both directions by default, but we can control the direction
# by specifying the vector direction. Here, we thicken upwards by 2 units.
thickened_solid = fused_face.thicken(0.1) # 'both=False' ensures thickening in one direction
# show(thickened_solid)
# Step 12: Fuse the thickened solid with the base model
result_model = base_model.add(thickened_solid)
# Step 13: Display the result for visualization
show(result_model) |
Beta Was this translation helpful? Give feedback.
-
Here is one way to achieve this semiconductor 3D CAD creation. There probably is a much more efficient way to do this. Sharing my hacky implementation for anyone who comes along the same path. import cadquery as cq
from cadquery.occ_impl.shapes import polyline, face, extrude
from cadquery import Vector
from cadquery.vis import show
# Helper Function
def get_box_dimensions(polygon):
"""
Calculates the length and width of a rectangle polygon.
:param polygon: List of (x, y) tuples defining the rectangle.
:return: Tuple (length, width)
"""
xs = [point[0] for point in polygon]
ys = [point[1] for point in polygon]
length = max(xs) - min(xs)
width = max(ys) - min(ys)
return length, width
# Core Functions
def create_splitting_solid(polygon_points, z_position, depth):
"""
Creates a splitting solid by extruding a polygon face downward.
:param polygon_points: List of points defining the polygon.
:param z_position: Z-coordinate where the polygon face is positioned.
:param depth: Depth to extrude downward (positive value).
:return: The splitting solid.
"""
# Create a closed wire using the polyline function
polygon_wire = polyline(*polygon_points).close()
# Convert the wire to a face and move it to the specified z_position
polygon_face = face(polygon_wire).moved(cq.Location(Vector(0, 0, z_position)))
# Extrude the polygon face downward to create a splitting solid
splitting_solid = extrude(polygon_face, (0, 0, -depth))
return splitting_solid
def intersect_with_base_current(base_current, splitting_solid):
"""
Finds the intersection between the base_current and the splitting solid.
:param base_current: The current base model (substrate plus previous deposits).
:param splitting_solid: The splitting solid.
:return: The intersected shape.
"""
# Find the intersection between the base_current and the splitting solid
intersected_shape = base_current.intersect(splitting_solid)
return intersected_shape
def extract_and_fuse_faces(intersected_shape, z_threshold):
"""
Extracts faces from the intersected shape that are above a certain z-coordinate and fuses them.
:param intersected_shape: The shape resulting from the intersection.
:param z_threshold: The minimum z-coordinate for faces to be included.
:return: The fused face.
"""
# Get all faces from the intersection
intersected_faces = intersected_shape.faces()
# Filter faces located above z_threshold
faces_above_threshold = []
for f in intersected_faces:
center = f.Center()
if center.z >= z_threshold:
faces_above_threshold.append(f)
# Ensure we have faces to work with
if not faces_above_threshold:
raise ValueError(f"No faces found above z = {z_threshold}.")
# Fuse all relevant faces into a single face
fused_shape = faces_above_threshold[0]
for face_to_fuse in faces_above_threshold[1:]:
fused_shape = fused_shape.fuse(face_to_fuse)
# Wrap the fused shape into a CadQuery Face object
fused_face = cq.Face(fused_shape.wrapped)
return fused_face
def thicken_face(fused_face, thickness):
"""
Thickens the fused face upwards to create a new solid.
:param fused_face: The fused face to thicken.
:param thickness: Thickness to thicken upwards.
:return: The thickened solid.
"""
# Thicken the fused face upwards to create a new solid
thickened_solid = fused_face.thicken(thickness)
return thickened_solid
# Polygons Definitions
# Substrate polygon (defines the base cube)
substrate_polygon = [
(-5, -5),
(5, -5),
(5, 5),
(-5, 5)
]
# Blind cut polygon (for the top face cut)
blind_cut_polygon = [
(-2.5, -2.5),
(2.5, -2.5),
(2.5, 2.5),
(-2.5, 2.5)
]
# Blind cut polygon2 (for the top face cut)
blind_cut_plygon2 = [
(-2, -2),
(2, -2),
(2, 2),
(-2, 2)
]
# Coating1 polygon
a, b = (0.75, 4.5)
coating1_polygon = [
(-a, -b),
(a, -b),
(a, b),
(-a, b)
]
# Coating2 polygon (rotated version of Coating1)
coating2_polygon = [
(-b, -a),
(b, -a),
(b, a),
(-b, a)
]
# Layers Definitions
substrate_depth = 5
tool_depth = substrate_depth
z_threshold_global = (substrate_depth/4)
layers = [
{
'name': 'Substrate',
'polygon_points': substrate_polygon,
'z_position': 0, # Centered at origin, so starting at -height/2
'depth': substrate_depth, # Height of the substrate
'thickness': 0.0, # No thickening needed
'z_threshold': -2.5, # Include all faces
'operation': 'substrate', # Substrate operation
'color': 'gray'
},
{
'name': 'BlindCut',
'polygon_points': blind_cut_polygon,
'z_position': 2.5, # Top of the substrate
'depth': tool_depth, # Depth of the cutting solid
'thickness': -0.25, # Thickness to thicken
'z_threshold': z_threshold_global, # Faces above this z will be included
'operation': 'etch', # Blind cut is an etch
'color': 'darkgray'
},
{
'name': 'BlindCut2',
'polygon_points': blind_cut_plygon2,
'z_position': 2.5, # Top of the substrate
'depth': tool_depth, # Depth of the cutting solid
'thickness': -0.25, # Thickness to thicken
'z_threshold': z_threshold_global, # Faces above this z will be included
'operation': 'etch', # Blind cut is an etch
'color': 'darkgray'
},
{
'name': 'Coating1',
'polygon_points': coating1_polygon,
'z_position': 2.5, # Top of the substrate
'depth': tool_depth, # Depth of cutting solid
'thickness': 0.1, # Thickness to thicken
'z_threshold': z_threshold_global, # Faces above this z will be included
'operation': 'deposit', # Deposit operation
'color': 'gold'
},
{
'name': 'Coating2',
'polygon_points': coating2_polygon,
'z_position': 2.5,
'depth': tool_depth,
'thickness': 0.1,
'z_threshold': z_threshold_global,
'operation': 'deposit',
'color': 'goldenrod'
},
# Add more layers as needed
]
# Initialize the coatings list
coatings_list = []
# Initialize the assembly
assy = cq.Assembly()
# Initialize the base model and base_current
substrate = None
base_current = None
# Loop over the layers
for layer in layers:
if layer['operation'] == 'substrate':
# Create the substrate using the substrate_polygon
length, width = get_box_dimensions(layer['polygon_points'])
# Create the substrate box centered about Z
substrate = cq.Workplane("XY").box(length, width, layer['depth'], centered=True)
# Update base_current with the substrate
base_current = substrate
show(base_current)
continue # Move to the next layer
elif layer['operation'] == 'etch':
# Create the splitting solid
splitting_solid = create_splitting_solid(
polygon_points=layer['polygon_points'],
z_position=layer['z_position'],
depth=layer['depth']
)
show(base_current,splitting_solid)
# Find the intersection with base_current
intersected_shape = intersect_with_base_current(base_current, splitting_solid)
# show(intersected_shape)
# Extract and fuse faces above z_threshold
fused_face = extract_and_fuse_faces(
intersected_shape=intersected_shape,
z_threshold=layer['z_threshold']
)
# show(fused_face)
# Thicken the fused face upwards to create a cut_solid
cut_solid = thicken_face(
fused_face=fused_face,
thickness=layer['thickness']
)
show(base_current,cut_solid)
# Update the substrate by cutting the cut_solid
substrate = substrate.cut(cut_solid)
# Update base_current to reflect the modified substrate
base_current = substrate
show(base_current)
elif layer['operation'] == 'deposit':
# Create the splitting solid
splitting_solid = create_splitting_solid(
polygon_points=layer['polygon_points'],
z_position=layer['z_position'],
depth=layer['depth']
)
show(base_current, splitting_solid)
# Find the intersection with base_current
intersected_shape = intersect_with_base_current(base_current, splitting_solid)
# show(base_current, intersected_shape)
# Extract and fuse faces above z_threshold
fused_face = extract_and_fuse_faces(
intersected_shape=intersected_shape,
z_threshold=layer['z_threshold']
)
show(fused_face)
show(base_current, fused_face)
# Thicken the fused face upwards to create an extrude_solid
extrude_solid = thicken_face(
fused_face=fused_face,
thickness=layer['thickness']
)
show(base_current, extrude_solid)
# Update base_current by unioning it with the extrude_solid
base_current = base_current.union(extrude_solid)
# Append the layer name and extrude_solid to coatings_list
coatings_list.append({'name': layer['name'], 'solid': extrude_solid, 'color': layer['color']})
else:
raise ValueError(f"Unknown operation: {layer['operation']}")
# Create the assembly
# Add the updated substrate to the assembly (after all etches)
assy.add(substrate, name='Substrate', color=cq.Color('gray'))
# Add each coating to the assembly
for coating in coatings_list:
assy.add(coating['solid'], name=coating['name'], color=cq.Color(coating['color']))
# Display the assembly
show(assy)
#assy.export('semiconductor_example.step') Feel free to use, improvise. |
Beta Was this translation helpful? Give feedback.
-
@adam-urbanczyk This question can be marked as answered |
Beta Was this translation helpful? Give feedback.
Here is one way to achieve this semiconductor 3D CAD creation. There probably is a much more efficient way to do this. Sharing my hacky implementation for anyone who comes along the same path.