1. Scientific context
The project is part of the Bio-Inspired Systems team work on soft pneumatic actuators. The objective is to use temporary hydrogel molds to create internal cavities in silicone, inspired by plant motion such as Mimosa pudica.
Internship project: Elyas - Bio-Inspired Systems team, Institut des Sciences du Mouvement. Fabrication of soft pneumatic actuators inspired by plant motion using printed temporary hydrogel molds.
The project is part of the Bio-Inspired Systems team work on soft pneumatic actuators. The objective is to use temporary hydrogel molds to create internal cavities in silicone, inspired by plant motion such as Mimosa pudica.
The method uses a temporary hydrogel structure printed inside a mold. Silicone is poured around the hydrogel, cured, and then the hydrogel is removed to leave pneumatic channels and chambers inside the elastomer.
Preparation of the silicone components before mixing and casting around the temporary hydrogel mold.
Degassing limits air bubbles in the silicone before or during casting.
The first prototype used a 32-cell architecture. Several trials were required to improve the number of functional cells and the quality of the pneumatic network.
Molding box used to structure the 32-cell prototype before casting silicone.
The printed hydrogel geometry is designed to create pneumatic chambers and channels inside the silicone part.
Example of a silicone part obtained after molding around the hydrogel structure.
Additional media show the actuator during manual pneumatic testing, the printed cell grid and the molded part held in its frame.
Manual syringe test used to inflate or check the soft actuator and its pneumatic connection.
Hydrogel grid showing the cell pattern before or after integration in the silicone process.
Prototype held in a dark frame, showing the repeated internal cell pattern.
Video showing a hydrogel or soft-actuator test during manipulation.
Second video showing the behavior of the hydrogel-based prototype during testing.
The molding box was redesigned to make fabrication more reliable. The work also included a fabrication notice and a lab notebook to make the process easier to reproduce.
Hydrogel printing prepares the temporary geometry that will form the internal pneumatic cavities.
Nozzle-travel speed tests were performed to reduce the printed filament diameter and improve the quality of the hydrogel geometry.
Diagram associated with the G-code development, used to organize cells, channels and printing paths.
A Python notebook was used to generate the G-code trajectories. This makes it possible to control the number of cells, the geometry of the network and the printing sequence.
Current Python code extracted from the Jupyter notebook used to generate G-code trajectories for hydrogel printing.
# %% Cellule 0import mathimport numpy as npimport os def generer_gcode_frc_cube(nom_fichier, a,b,coordonnees_start,extrusion_continu, epaisseur_fil, hauteur_couche,simulate=False): """ Génère un G-code pour parcourir le volume d'un cube en spirale alternée avec extrusion constante. :param nom_fichier: Nom du fichier de sortie (str). :param a: largeur (float) en mm. :param b: hauteur (float) en mm. :param coordonnees_start: Coordonées du point de départ du cube (list). :param epaisseur_fil: Épaisseur du fil extrudé (float) en mm. :param hauteur_couche: Hauteur de chaque couche (float) en mm. """ #=== paramètre est-ce qu'on extrude le cube en premier ou si il vient après autre chose===== # nom_fichier.write(f"G0 Z{coordonnees_start[2]-40:.2f} ;\n") # nom_fichier.write(f"G0 X{coordonnees_start[0]:.2f} Y{coordonnees_start[1]:.2f} Z{coordonnees_start[2]:.2f} ;\n") demi_taille = a / 2 nb_couches = round(b / hauteur_couche) spirales_par_couche = round(demi_taille / epaisseur_fil) extrusion = extrusion_continu # Position initiale de l'extrudeur for couche in range(nb_couches): z=coordonnees_start[2]-couche*hauteur_couche if not simulate: nom_fichier.write("\n") nom_fichier.write(f"G0 Z{z:.2f} F800;\n") # print(f"Z{z:.2f}") # Déterminer si la spirale est concentrique ou excentrique concentrique = (couche % 2 == 0) for spirale in range(spirales_par_couche): r1 = spirale * epaisseur_fil r2 = (spirale + 1) * epaisseur_fil if not concentrique: r1, r2 = r2, r1 # Alterner concentrique/excentrique # Points de la spirale points = [ (-r1, -r1), (r2, -r1), (r2, r2), (-r1, r2), (-r1, -r1) # Retour au point de départ ] # Générer les commandes G1 pour chaque segment for i in range(len(points) - 1): x_cube_st, y1 = points[i] x2, y2 = points[i + 1] # Calcul de la distance et de la vitesse réelle de la buse distance = math.sqrt((x2 - x_cube_st) ** 2 + (y2 - y1) ** 2) #vitesse_buse = vitesse_extrusion / facteur_extrusion #temps_deplacement = distance / vitesse_buse * 60 # Temps en secondes #vitesse_deplacement = distance / temps_deplacement * 60 # Vitesse en mm/min extrusion += (distance*0.45**2)/(10**2)*1.75 if not simulate: nom_fichier.write( f"G1 X{x2 + demi_taille + coordonnees_start[0]:.2f} " f"Y{y2 + demi_taille + coordonnees_start[1]:.2f} " f"E{extrusion:.3f}\n" ) # print(f" X{x2 + demi_taille + coordonnees_start[0]:.2f} "f"Y{y2 + demi_taille + coordonnees_start[1]:.2f} "f"E{extrusion:.3f}\n") #print(f"G-code généré et sauvegardé dans {nom_fichier}") # retourner la derniere position de la buse return x2 + demi_taille + coordonnees_start[0],y2 + demi_taille + coordonnees_start[1],z - coordonnees_start[2], extrusion def maximize_min_distance(points, num_points): def distance(p1, p2): return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) def can_place_points(min_distance): count = 1 last_point = points[0] for i in range(1, len(points)): if distance(points[i], last_point) >= min_distance: count += 1 last_point = points[i] if count == num_points: return True return False # Trier les points par coordonnée x (ou y) pour faciliter le calcul points.sort() # Initialiser les bornes pour la recherche binaire low, high = 0, distance(points[0], points[-1]) best_distance = 0 best_positions = [] while low <= high: mid = (low + high) / 2 if can_place_points(mid): best_distance = mid low = mid + 1 else: high = mid - 1 # Reconstruire la liste des positions choisies avec la meilleure distance last_point = points[0] best_positions.append(last_point) count = 1 for i in range(1, len(points)): if distance(points[i], last_point) >= best_distance: best_positions.append(points[i]) last_point = points[i] count += 1 if count == num_points: break return best_positionsdef calcul_extrud_by_points(point_dep,point_arriv): x1, y1 = point_dep[0],point_dep[1] x2, y2 = point_arriv[0],point_arriv[1] # Calcul de la distance et de la vitesse réelle de la buse distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) extrusion = (distance*0.45**2)/(10**2) return extrusiondef calcul_extrud_by_dist(dist): extrusion = (dist*0.45**2)/(10**2) return extrusiondef format_nombre(n): if int(n) == n: return str(int(n)) # pas de .0 else: return ('%g' % n) # notation courte def tracer_ligne(fichier, x1, y1, z, x2, y2, extrusion_continu): dist = ((x2 - x1)**2 + (y2 - y1)**2)**0.5 # Nombre de segments de 30 mm + reste nb_segments = int(dist // 10) reste = dist % 10 # Vecteur direction normalisé dx = (x2 - x1) / dist dy = (y2 - y1) / dist # Fonction interne pour tracer un segment def tracer_segment(x_depart, y_depart, longueur, extrusion_continu): extrusion_continu += calcul_extrud_by_dist(longueur) * 1.5 x_arrivee = x_depart + dx * longueur y_arrivee = y_depart + dy * longueur fichier.write( f"G1 X{format_nombre(x_arrivee)} " f"Y{format_nombre(y_arrivee)} " f"Z{format_nombre(z)} " f"E{extrusion_continu:.3f};\n" ) return x_arrivee, y_arrivee, extrusion_continu # Position courante x_courant, y_courant = x1, y1 # Tracer les segments complets de 10 mm for _ in range(nb_segments): x_courant, y_courant, extrusion_continu = tracer_segment(x_courant, y_courant, 10, extrusion_continu) # Tracer le segment final (reste) if reste > 0: x_courant, y_courant, extrusion_continu = tracer_segment(x_courant, y_courant, reste, extrusion_continu) return extrusion_continu import plotly.graph_objects as go def parse_gcode_line(line): """Analyse une ligne G-code et retourne le type (G0/G1) et les coordonnées X, Y, Z.""" cmd = None x = y = z = None parts = line.strip().split() for part in parts: if part.startswith('G'): cmd = part elif part.startswith('X'): x = float(part[1:]) elif part.startswith('Y'): y = float(part[1:]) elif part.startswith('Z'): z = float(part[1:]) return cmd, x, y, z def charger_trajectoires(fichier): with open(fichier, 'r') as f: lignes = f.readlines() position_actuelle = [0.0, 0.0, 0.0] lignes_G0 = [] # Vert lignes_G1 = [] # Bleu for ligne in lignes: cmd, x, y, z = parse_gcode_line(ligne) x = position_actuelle[0] if x is None else x y = position_actuelle[1] if y is None else y z = position_actuelle[2] if z is None else z nouvelle_position = [x, y, z] if cmd == 'G0': lignes_G0.append((position_actuelle, nouvelle_position)) elif cmd == 'G1': lignes_G1.append((position_actuelle, nouvelle_position)) position_actuelle = nouvelle_position return lignes_G0, lignes_G1 def tracer_plotly_3d(fichier): lignes_G0, lignes_G1 = charger_trajectoires(fichier) traces = [] def add_traces(lignes, couleur, nom): for start, end in lignes: trace = go.Scatter3d( x=[start[0], end[0]], y=[start[1], end[1]], z=[start[2], end[2]], mode='lines', line=dict(color=couleur, width=4), name=nom, showlegend=False ) traces.append(trace) add_traces(lignes_G0, 'green', 'G0') add_traces(lignes_G1, 'blue', 'G1') fig = go.Figure(data=traces) # Point de départ (rouge) first_point = lignes_G1[0][0] if lignes_G1 else lignes_G0[0][0] fig.add_trace(go.Scatter3d( x=[first_point[0]], y=[first_point[1]], z=[first_point[2]], mode='markers', marker=dict(size=6, color='red'), name='Départ' )) # Point d’arrivée (noir) last_point = lignes_G1[-1][1] if lignes_G1 else lignes_G0[-1][1] fig.add_trace(go.Scatter3d( x=[last_point[0]], y=[last_point[1]], z=[last_point[2]], mode='markers', marker=dict(size=6, color='black'), name='Arrivée' )) fig.update_layout( scene=dict( xaxis_title='X', yaxis_title='Y', zaxis_title='Z', ), margin=dict(l=0, r=0, b=0, t=30) ) # Déterminer les plages min/max pour chaque axe all_x = [pt for ligne in lignes_G0 + lignes_G1 for pt in [ligne[0][0], ligne[1][0]]] all_y = [pt for ligne in lignes_G0 + lignes_G1 for pt in [ligne[0][1], ligne[1][1]]] all_z = [pt for ligne in lignes_G0 + lignes_G1 for pt in [ligne[0][2], ligne[1][2]]] min_x, max_x = min(all_x), max(all_x) min_y, max_y = min(all_y), max(all_y) min_z, max_z = min(all_z), max(all_z) # Calcul du centre et de la plage maximale center_x = (min_x + max_x) / 2 center_y = (min_y + max_y) / 2 center_z = (min_z + max_z) / 2 max_range = max(max_x - min_x, max_y - min_y, max_z - min_z) / 2 fig.update_layout( scene=dict( xaxis=dict(nticks=10, range=[center_x - max_range, center_x + max_range]), yaxis=dict(nticks=10, range=[center_y - max_range, center_y + max_range]), zaxis=dict(nticks=10, range=[center_z + max_range,center_z - max_range]), aspectmode='cube', xaxis_title='X', yaxis_title='Y', zaxis_title='Z', ), ) fig.show() def y_by_indice(indice, t, a): """ Y varie avec l indice au sein d une colonne. Deux colonnes (8 indices par colonne) """ colonne = 0 if indice <= 8 else 1 return t + ((indice - 1) % 8) * (t + a) def x_by_indice(indice, t, a): """ X change uniquement lorsqu on passe à la colonne suivante. """ colonne = 0 if indice <= 8 else 1 return t + colonne * (t + a) # %% Cellule 1def generer_gcode_spirale_continue(nom_fichier, a, b, coordonnees_start, extrusion_continu, epaisseur_fil, hauteur_couche, simulate=False): """ Génère un G-code pour un cube parcouru en spirale carrée continue (sans alternance). """ demi_taille = a / 2 nb_couches = round(b / hauteur_couche) spirales_par_couche = round(demi_taille / epaisseur_fil) extrusion = extrusion_continu for couche in range(nb_couches): z = coordonnees_start[2] - couche * hauteur_couche if not simulate: nom_fichier.write("\n") nom_fichier.write(f"G0 Z{z:.2f} F800;\n") for spirale in range(spirales_par_couche): r = (spirale + 1) * epaisseur_fil points = [ (-r, -r), (r, -r), (r, r), (-r, r), (-r, -r) ] for i in range(len(points) - 1): x1, y1 = points[i] x2, y2 = points[i + 1] distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) extrusion += (distance * 0.45**2) / (10**2) * 1.75 if not simulate: nom_fichier.write( f"G1 X{x2 + demi_taille + coordonnees_start[0]:.2f} " f"Y{y2 + demi_taille + coordonnees_start[1]:.2f} " f"E{extrusion:.3f}\n" ) return x2 + demi_taille + coordonnees_start[0], y2 + demi_taille + coordonnees_start[1], z - coordonnees_start[2], extrusion # %% Cellule 2def count_until_e(line: str) -> int: """ Compte la longueur de la ligne jusqu'au premier 'E' inclus. Si pas de 'E', retourne la longueur totale. """ idx = line.find("E") if idx != -1: return idx + 1 return len(line) def check_gcode_file(filepath: str, limit: int = 30): """ Ouvre le fichier GCODE et affiche les lignes qui dépassent la limite de caractères avant 'E'. """ too_long_lines = [] with open(filepath, "r", encoding="utf-8") as f: for lineno, line in enumerate(f, start=1): line = line.rstrip("\n") # enlever le retour chariot length = count_until_e(line) if length > limit: too_long_lines.append((lineno, length, line)) if too_long_lines: print("⚠️ Lignes trop longues trouvées :") for lineno, length, content in too_long_lines: print(f" - Ligne {lineno} ({length} caractères avant E) : {content}") else: print("✔️ Aucune ligne trop longue") # %% Cellule 3import math def calcul_tps_impression(fichier_gcode: str) -> dict: """ Calcule la distance parcourue par la buse pour chaque étape, séparée par les lignes contenant 'SECHAGE'. """ position_actuelle = {"X": 0.0, "Y": 0.0, "Z": 0.0} distances = {} etape_index = 1 distances[f"etape_{etape_index}"] = 0.0 with open(fichier_gcode, "r", encoding="utf-8") as f: for ligne in f: ligne = ligne.strip() # Ignore les commentaires classiques if not ligne or ligne.startswith(";"): continue # Si on rencontre une nouvelle étape (mot clé SECHAGE) if "SECHAGE" in ligne.upper(): etape_index += 1 distances[f"etape_{etape_index}"] = 0.0 continue # Vérifie si c'est un G0 ou G1 if ligne.startswith(("G0", "G1")): nouvelle_position = position_actuelle.copy() # Recherche coordonnées X, Y, Z for axe in ["X", "Y", "Z"]: if axe in ligne: try: valeur = float(ligne.split(axe)[1].split()[0]) nouvelle_position[axe] = valeur except ValueError: pass # Si c'est un G1 → calcul distance if ligne.startswith("G1"): dx = nouvelle_position["X"] - position_actuelle["X"] dy = nouvelle_position["Y"] - position_actuelle["Y"] dz = nouvelle_position["Z"] - position_actuelle["Z"] distance = math.sqrt(dx*dx + dy*dy + dz*dz) distances[f"etape_{etape_index}"] += distance # Met à jour la position position_actuelle = nouvelle_position return distances # %% Cellule 4def definir_sequence_couche(couche): """ Retourne la séquence d’impression (ordre et indices) pour chaque couche. """ if couche == 1: return [ {"nom": "fractions", "indices": [1,2,3,4,5,6,9,10,11,12,13,14], "type": "fraction"}, {"nom": "cases_pleines", "indices": [7,8,15,16], "type": "pleine"},#[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] {"nom": "canaux", "indices": [7,8,15,16], "type": "canal"}#[1,2,3,9,10,11] ] elif couche == 2: return [ {"nom": "fractions", "indices": [1,2,3,4,9,10,11,12], "type": "fraction"}, {"nom": "cases_pleines", "indices": [5,6,13,14], "type": "pleine"}, {"nom": "canaux", "indices": [5,6,13,14], "type": "canal"} ] elif couche == 3: return [ {"nom": "fractions", "indices": [1,2,9,10], "type": "fraction"}, {"nom": "cases_pleines", "indices": [3,4,11,12], "type": "pleine"}, {"nom": "canaux", "indices": [3,4,11,12], "type": "canal"} ] elif couche == 4: return [ {"nom": "cases_pleines", "indices": [1,2,9,10], "type": "pleine"}, {"nom": "canaux", "indices": [1,2,9,10], "type": "canal"} ] else: return [] # %% Cellule 5def generer_canal(fichier, indice_case,x_origine,y_origine,z, position, direction, t, a, extrusion_continu): """ position: 'premier', 'deuxieme', 'centre' direction: 'droite' ou 'gauche' """ zone_dechet=15 # --- Sélection de la position en X --- if position == "premier": x_canal =round(0.32*t,2) elif position == "deuxieme": x_canal = round(0.65*t,2) elif position == "centre": x_canal = a/2 else: raise ValueError(f"Position canal inconnue : {position}") # --- Longueur du canal selon la direction --- if direction == "droite": x_canal = x_canal elif direction == "gauche": if position == "premier": x_canal = -round(0.65*t,2) # valeur de 'deuxieme' elif position == "deuxieme": x_canal = -round(0.32*t,2) # valeur de 'premier' else: raise ValueError(f"Direction canal inconnue : {direction}") fichier.write(f"G0 Z{z-50}\n") # --- Tracé du canal --- if position == "centre": fichier.write(f"G0 X{x_origine+x_by_indice(indice_case,t,a)+x_canal} Y{y_origine-zone_dechet} F1000;\n") fichier.write(f"G0 Z{z} F1000;\n") extrusion_continu+=0.2 fichier.write(f"E{extrusion_continu:.3f} F1;\n") fichier.write("STOP 15000;\n") extrusion_continu = tracer_ligne( fichier, x_origine+x_by_indice(indice_case,t,a)+x_canal, y_origine-zone_dechet, z, x_origine+x_by_indice(indice_case,t,a)+x_canal, y_origine+y_by_indice(indice_case,t,a), extrusion_continu ) else: fichier.write(f"G0 X{x_origine+x_by_indice(indice_case,t,a)-t+x_canal} Y{y_origine-zone_dechet} F1000;\n") fichier.write(f"G0 Z{z} F1000;\n") extrusion_continu+=0.2 fichier.write(f"E{extrusion_continu:.3f} F1;\n") fichier.write("STOP 15000;\n") extrusion_continu = tracer_ligne( fichier, x_origine+x_by_indice(indice_case,t,a)-t+x_canal, y_origine-zone_dechet, z, x_origine+x_by_indice(indice_case,t,a)-t+x_canal, y_origine+y_by_indice(indice_case,t,a)+a/2, extrusion_continu ) extrusion_continu = tracer_ligne( fichier, x_origine+x_by_indice(indice_case,t,a)-x_canal, y_origine+y_by_indice(indice_case,t,a)+a/2, z, x_origine+x_by_indice(indice_case,t,a)-x_canal+x_canal, y_origine+y_by_indice(indice_case,t,a)+a/2, extrusion_continu ) return extrusion_continu # %% Cellule 6CONFIG_CANAUX = { 1: {"position": "centre", "direction": "droite"}, 2: {"position": "deuxieme", "direction": "droite"}, 3: {"position": "premier", "direction": "droite"}, 4: {"position": "deuxieme", "direction": "droite"}, 5: {"position": "premier", "direction": "droite"}, 6: {"position": "deuxieme", "direction": "droite"}, 7: {"position": "premier", "direction": "droite"}, 8: {"position": "premier", "direction": "droite"}, 9: {"position": "centre", "direction": "droite"}, 10: {"position": "deuxieme", "direction": "droite"}, 11: {"position": "premier", "direction": "droite"}, 12: {"position": "deuxieme","direction": "droite"}, 13: {"position": "premier","direction": "droite"}, 14: {"position": "deuxieme", "direction": "droite"}, 15: {"position": "premier", "direction": "droite"}, 16: {"position": "premier", "direction": "droite"}}CONFIG_CANAUXV2 = { 1: {"position": "centre", "direction": "droite"}, 2: {"position": "premier", "direction": "droite"}, 3: {"position": "deuxieme", "direction": "droite"}, 4: {"position": "premier", "direction": "droite"}, 5: {"position": "deuxieme", "direction": "droite"}, 6: {"position": "premier", "direction": "droite"}, 7: {"position": "deuxieme", "direction": "droite"}, 8: {"position": "premier", "direction": "droite"}, 9: {"position": "centre", "direction": "droite"}, 10: {"position": "premier", "direction": "droite"}, 11: {"position": "deuxieme", "direction": "droite"}, 12: {"position": "premier","direction": "droite"}, 13: {"position": "deuxieme","direction": "droite"}, 14: {"position": "premier", "direction": "droite"}, 15: {"position": "deuxieme", "direction": "droite"}, 16: {"position": "premier", "direction": "droite"}} # %% Cellule 7nom_fichier="8x2x2_trunkv5elyas2"extrusion_continu=0 x_origine=108-0.3y_origine=69z_origine=212 Nx=8Ny=2 a=15t=7.5surface_case=ahauteur_case=a x_premiercanal=round(0.32*t,2)x_deuxiemecanal=round(0.65*t,2) x_milieu_case=t+a/2 indice_case=0nombre_couche_tot=4 _,_,_,calcul_extrd=generer_gcode_spirale_continue(nom_fichier,surface_case,hauteur_case,[x_origine,y_origine,z_origine],extrusion_continu, 0.9, 1,True)recharge_serg=0 for couche in range(1,nombre_couche_tot+1): currentfichier = nom_fichier+f"_couche_{couche}.txt" with open(currentfichier, "w") as fichier: fichier.write(f"G0 Z{z_origine-100} F800;\n") fichier.write(f"G0 X{x_origine+t+a/2} Y{y_origine-15} F500;\n") fichier.write(f"G0 Z{z_origine-a*((couche-1)/nombre_couche_tot)} F800;\n") extrusion_continu+=0.4 fichier.write(f"E{extrusion_continu:.3f} F1;\n") fichier.write("STOP 30000;\n") SEQUENCE_IMPRESSION = definir_sequence_couche(couche) for etape in SEQUENCE_IMPRESSION: for indice_case in etape["indices"]: if etape["type"] == "fraction": fichier.write(f"G0 Z{z_origine-50} F800;\n") fichier.write(f"G0 X{x_origine+x_by_indice(indice_case,t,a)+a/2} Y{y_origine+y_by_indice(indice_case,t,a)+a/2} F500;\n") x_end,y_end,z_end,extrusion_continu=generer_gcode_spirale_continue(fichier,surface_case,hauteur_case*1/nombre_couche_tot,[x_origine+x_by_indice(indice_case,t,a),y_origine+y_by_indice(indice_case,t,a),z_origine-a*((couche-1)/nombre_couche_tot)],extrusion_continu, 0.9, 1) # fichier.write(f"G0 X{x_end+2} Y{y_end+2} ;\n") fichier.write(f"G0 Z{z_origine-50} F800;\n") if (extrusion_continu>=75-calcul_extrd): fichier.write(f"G0 Z{50} F800\n") fichier.write(f"G0 X{15} Y{15} F500;\n") fichier.write("RECHARGE\n") recharge_serg+=1 extrusion_continu=0 fichier.write(f"G0 Z{z_origine-50} F800;\n") fichier.write(f"G0 X{x_origine+t+a/2} Y{y_origine-15} F500;\n") fichier.write(f"G0 Z{z_origine-a*((couche-1)/nombre_couche_tot)} F800;\n") extrusion_continu+=0.4 fichier.write(f"E{extrusion_continu:.3f} F1;\n") fichier.write("STOP 30000;\n") elif etape["type"] == "pleine": fichier.write(f"G0 Z{z_origine-50} F800;\n") fichier.write(f"G0 X{x_origine+x_by_indice(indice_case,t,a)+a/2} Y{y_origine+y_by_indice(indice_case,t,a)+a/2} F500;\n") x_end,y_end,z_end,extrusion_continu=generer_gcode_spirale_continue(fichier,surface_case,hauteur_case*((5-couche)/nombre_couche_tot),[x_origine+x_by_indice(indice_case,t,a),y_origine+y_by_indice(indice_case,t,a),z_origine-a*((couche-1)/nombre_couche_tot)],extrusion_continu, 0.9, 1) fichier.write(f"G0 Z{z_origine-50} F800;\n") if (extrusion_continu>=75-calcul_extrd): fichier.write(f"G0 Z{50} F800\n") fichier.write(f"G0 X{15} Y{15} F500;\n") fichier.write("RECHARGE\n") recharge_serg+=1 extrusion_continu=0 fichier.write(f"G0 Z{z_origine-50} F800;\n") fichier.write(f"G0 X{x_origine+t+a/2} Y{y_origine-15} F500;\n") fichier.write(f"G0 Z{z_origine-a*((couche-1)/nombre_couche_tot)} F800;\n") extrusion_continu+=0.4 fichier.write(f"E{extrusion_continu:.3f} F1;\n") fichier.write("STOP 30000;\n") elif etape["type"] == "canal": # Récupération du mode de canal pour cette case canal_cfg = CONFIG_CANAUXV2[indice_case] fichier.write(f"G0 Z{z_origine-50} F1000;\n") if (indice_case<=8): fichier.write(f"G0 X{x_origine-20} F1000;\n") else : fichier.write(f"G0 X{x_origine + 2*a+3*t + 20} F1000;\n") fichier.write(f"G0 Y{y_origine} F1000;\n") z=z_origine-a*((couche-1)/nombre_couche_tot) extrusion_continu = generer_canal( fichier=fichier, indice_case=indice_case, x_origine=x_origine, y_origine=y_origine,z=z, position=canal_cfg["position"], direction=canal_cfg["direction"], t=t, a=a, extrusion_continu=extrusion_continu ) extrusion_continu+=0.1 fichier.write(f"E{extrusion_continu:.3f} F1;\n") fichier.write("STOP 3500;\n") fichier.write(f"G0 Z{z_origine-50} F800;\n") # déterminer coordonnées du canal associé # extrusion_continu = tracer_ligne(fichier, x_origine+x_by_indice(indice_case,t,a)-x_deuxiemecanal, y_origine, z_origine-a*((couche-1)/nombre_couche_tot), x_origine+x_by_indice(indice_case,t,a)-x_deuxiemecanal, y_origine+y_by_indice(indice_case,t,a)+a/2, extrusion_continu) # extrusion_continu = tracer_ligne(fichier, x_by_indice(indice_case,t,a)-x_premiercanal, y_origine+y_by_indice(indice_case,t,a), z_origine, x_by_indice(indice_case,t,a), y_origine+y_by_indice(indice_case,t,a), extrusion_continu) fichier.write(f"G0 Z{z_origine-50} F1000\n") fichier.write(f"G0 X{15} Y{15} F1000;\n") fichier.write("SECHAGE\n") extrusion_continu=0 with open(nom_fichier + ".txt", "w") as outfile: for couche in range(1, nombre_couche_tot + 1): currentfichier = nom_fichier + f"_couche_{couche}.txt" with open(currentfichier) as infile: for line in infile: outfile.write(line) chemin = nom_fichier + ".txt" check_gcode_file(chemin, limit=30)resultats = calcul_tps_impression(chemin)for etape, dist in resultats.items(): print(f"{etape} : {dist/110:.2f} min || {dist/110//60:.0f}h{round(dist/110%60):.0f}")print(f"{recharge_serg} recharge de seringue d_hydrogel seront necessaire pour effectuer le fichier {nom_fichier}") # %% Cellule 8import osfiles = os.listdir("/Users/elyashariiz/Desktop/programmation/stage2a/test3/niveau_2_v4")for f in files: if "trunkv3" in f: print(f) # %% Cellule 11a = 0.1 + 0.2 #certains nombres décimaux simples comme 0.1, 0.2, 0.3 ne peuvent pas être représentés exactement en binaire.#C’est une limite des nombres flottants (floating point, norme IEEE 754)a # %% Cellule 12import re def ajuster_valeurs_z(fichier_entree, fichier_sortie, decalage): """ Lit un fichier texte contenant des lignes G-code et soustrait 'decalage' à chaque valeur de Z. Le résultat est enregistré dans un nouveau fichier. """ with open(fichier_entree, 'r') as f: contenu = f.read() # Expression régulière pour trouver les valeurs de Z (ex: Z248.15) pattern = r'Z(\d+\.?\d*)' def remplacer(match): valeur = float(match.group(1)) nouvelle_valeur = valeur - decalage # on soustrait le décalage return f"Z{str(round(1e2*(nouvelle_valeur))/100)}" # Remplacement dans tout le texte contenu_modifie = re.sub(pattern, remplacer, contenu) # Sauvegarde dans un nouveau fichier with open(fichier_sortie, 'w') as f: f.write(contenu_modifie) print(f"✅ Fichier modifié enregistré sous : {fichier_sortie}") ajuster_valeurs_z("/Users/elyashariiz/Desktop/programmation/stage2a/test4/8x2x2_trunkv5elyas2_couche_4.txt", "/Users/elyashariiz/Desktop/programmation/stage2a/test4/8x2x2_trunkv5elyas2_couche_4.txt", -2.25) # %% Cellule 13import osfiles = os.listdir()print(files)
The work later moved toward a 49-cell prototype arranged in a 7 by 7 by 1 configuration with an interconnected pneumatic network and a single air inlet.
The CAD archive contains the mold versions used for the hydrogel-actuator project. Selected STL files are displayed directly on the page, while the full archive remains available for the CATIA and technical files.
Downloadable archive containing the CAD files of the hydrogel-actuator molds.
Representative mold and base STL files extracted from the CAD archive for direct 3D preview.
This project combines additive manufacturing, silicone molding, hydrogel printing, pneumatic actuation and Python programming to fabricate soft bio-inspired actuators.
Use this button to open the print dialog, then choose “Save as PDF”.