diff --git a/slides.nav b/slides.nav deleted file mode 100644 index 145a2ae..0000000 --- a/slides.nav +++ /dev/null @@ -1,40 +0,0 @@ -\headcommand {\slideentry {0}{0}{1}{1/1}{}{0}} -\headcommand {\beamer@framepages {1}{1}} -\headcommand {\beamer@sectionpages {1}{1}} -\headcommand {\beamer@subsectionpages {1}{1}} -\headcommand {\sectionentry {1}{Positionnement du Problème}{2}{Positionnement du Problème}{0}} -\headcommand {\slideentry {1}{0}{1}{2/2}{}{0}} -\headcommand {\beamer@framepages {2}{2}} -\headcommand {\slideentry {1}{0}{2}{3/3}{}{0}} -\headcommand {\beamer@framepages {3}{3}} -\headcommand {\slideentry {1}{0}{3}{4/5}{}{0}} -\headcommand {\beamer@framepages {4}{5}} -\headcommand {\slideentry {1}{0}{4}{6/6}{}{0}} -\headcommand {\beamer@framepages {6}{6}} -\headcommand {\beamer@sectionpages {2}{6}} -\headcommand {\beamer@subsectionpages {2}{6}} -\headcommand {\sectionentry {2}{Implémentation}{7}{Implémentation}{0}} -\headcommand {\slideentry {2}{0}{1}{7/7}{}{0}} -\headcommand {\beamer@framepages {7}{7}} -\headcommand {\slideentry {2}{0}{2}{8/8}{}{0}} -\headcommand {\beamer@framepages {8}{8}} -\headcommand {\slideentry {2}{0}{3}{9/9}{}{0}} -\headcommand {\beamer@framepages {9}{9}} -\headcommand {\slideentry {2}{0}{4}{10/10}{}{0}} -\headcommand {\beamer@framepages {10}{10}} -\headcommand {\slideentry {2}{0}{5}{11/11}{}{0}} -\headcommand {\beamer@framepages {11}{11}} -\headcommand {\beamer@sectionpages {7}{11}} -\headcommand {\beamer@subsectionpages {7}{11}} -\headcommand {\sectionentry {3}{Réalisme}{12}{Réalisme}{0}} -\headcommand {\slideentry {3}{0}{1}{12/12}{}{0}} -\headcommand {\beamer@framepages {12}{12}} -\headcommand {\slideentry {3}{0}{2}{13/13}{}{0}} -\headcommand {\beamer@framepages {13}{13}} -\headcommand {\slideentry {3}{0}{3}{14/14}{}{0}} -\headcommand {\beamer@framepages {14}{14}} -\headcommand {\beamer@partpages {1}{14}} -\headcommand {\beamer@subsectionpages {12}{14}} -\headcommand {\beamer@sectionpages {12}{14}} -\headcommand {\beamer@documentpages {14}} -\headcommand {\gdef \inserttotalframenumber {13}} diff --git a/slides.pdf b/slides.pdf deleted file mode 100644 index 4141d14..0000000 Binary files a/slides.pdf and /dev/null differ diff --git a/slides.snm b/slides.snm deleted file mode 100644 index 9047784..0000000 --- a/slides.snm +++ /dev/null @@ -1,3 +0,0 @@ -\beamer@slide {sec:le-probleme}{3} -\beamer@slide {fig:cnc}{3} -\beamer@slide {sec:l'implementation}{8} diff --git a/slides.tex b/slides.tex deleted file mode 100644 index 8b9c67b..0000000 --- a/slides.tex +++ /dev/null @@ -1,227 +0,0 @@ -% Preamble -\documentclass{beamer} -\usepackage{cmap} -\usepackage[french]{babel} -\usepackage{amsmath, amssymb} -\usepackage{tikz} -\usepackage{multicol} -\usetikzlibrary{arrows.meta, backgrounds, calc, fit, positioning, scopes, shadows} - - -\mode -\usetheme{Dresden} - -\definecolor{vulm}{HTML}{7d1dd3} -\definecolor{yulm}{HTML}{ffe500} - -\AtBeginSection[]{\begin{frame}{Plan}\tableofcontents[sectionstyle=show/shaded]\end{frame}} - -\setbeamercolor{structure}{fg = vulm} -\setbeamercolor{section in head/foot}{fg = yulm} -\setbeamercolor{subsection in head/foot}{fg = yulm} -\setbeamercolor{background canvas}{bg = yulm!10} -\setbeamertemplate{navigation symbols}{} - -\author{Matthieu \textsc{Boyer} \& Sélène \textsc{Corbineau}} -\title{Simulation d'un Contrôleur de Petite Machine à Bois} - -% Document -\begin{document} -\maketitle -\section{Positionnement du Problème}\label{sec:le-probleme} - -\begin{frame} - \frametitle{Petite Machine à Bois} - \begin{figure} - \centering - \includegraphics[width=.9\textwidth]{images/cnc} - \caption{Une Petite Machine à Bois} - \label{fig:cnc} - \end{figure} -\end{frame} - -\begin{frame} - \frametitle{Format d'Entrée} - On convertit un fichier \texttt{.svg} en fichier \texttt{.gc} qui est une abstraction des commandes à exécuter. - \begin{figure} - \centering - \begin{tikzpicture} - \draw[vulm, rounded corners, fill=yulm!30] (0, 0) rectangle +(2, 2) node[pos=.5] {\texttt{.svg}}; - \draw[vulm, ->] (2, 1) -- (3, 1); - \draw[vulm, rounded corners, fill=yulm!30] (3, 0) rectangle +(2, 2) node[pos=.5] {\texttt{.gc}}; - \draw[vulm, ->] (5, 1) -- (6, 1); - \draw[vulm, rounded corners, fill=yulm!30, double] (6, 0) rectangle +(2, 2) node[pos=.5] {\texttt{PMB}}; - \end{tikzpicture} - \end{figure} - \visible<2->{Le format GCode est standardisé, et indépendant des capacités de la machine.} -\end{frame} - -\tikzset{ - comp/.style = { - minimum width = 8cm, - minimum height = 4.5cm, - text width = 8cm, - inner sep = 0pt, - text = green, - align = center, - font = \Huge, - transform shape, - thick - }, - monitor/.style = {draw = none, xscale = 18/16, yscale = 11/9}, - display/.style = {shading = axis, left color = black!60, right color = black}, - ut/.style = {fill = gray} -} - -\tikzset{ - computer/.pic = { - % screen (with border) - \node(-m) [comp, pic actions, monitor] - {\phantom{\parbox{\linewidth}{\tikzpictext}}}; - % display (without border) - \node[comp, pic actions, display] {\tikzpictext}; - \begin{scope}[x = (-m.east), y = (-m.north)] - % filling the lower part - \path[pic actions, draw = none] - ([yshift=2\pgflinewidth]-0.1,-1) -- (-0.1,-1.3) -- (-1,-1.3) -- - (-1,-2.4) -- (1,-2.4) -- (1,-1.3) -- (0.1,-1.3) -- - ([yshift=2\pgflinewidth]0.1,-1); - % filling the border of the lower part - \path[ut] - (-1,-2.4) rectangle (1,-1.3) - (-0.9,-1.4) -- (-0.7,-2.3) -- (0.7,-2.3) -- (0.9,-1.4) -- cycle; - % drawing the frame of the whole computer - \path[pic actions, fill = none] - (-1,1) -- (-1,-1) -- (-0.1,-1) -- (-0.1,-1.3) -- (-1,-1.3) -- - (-1,-2.4) coordinate(sw)coordinate[pos=0.5] (-b west) -- - (1,-2.4) -- (1,-1.3) coordinate[pos=0.5] (-b east) -- - (0.1,-1.3) -- (0.1,-1) -- (1,-1) -- (1,1) -- cycle; - % node around the whole computer - \node(-c) [fit = (sw)(-m.north east), inner sep = 0pt] {}; - \end{scope} - } -} - -\begin{frame} - \frametitle{Communication} - \begin{figure} - \centering - \hfill - \begin{minipage}{.5\textwidth} - \begin{tikzpicture} - \pic(comp0) [ - draw, fill = gray!30, pic text = {Contrôleur de Petite Machine à Bois}, scale=.25 - ] {computer}; - \coordinate (cnc) at ($(comp0-c.south) + (0, -1)$); - \path (comp0-c.east) edge [loop right, min distance=.7cm, in=330, out=30, looseness=8, -{Latex[width=3mm]}, vulm] node[right, vulm, rectangle, fill=yulm!30, draw=vulm, rounded corners] {GCode} (comp0-c.east); - \draw[vulm, fill=yulm!30, rounded corners] ($(cnc) + (-1, 0)$) rectangle +(2, -1) node[pos=0.5] {Socket}; - \draw[vulm, ->, double] ($(comp0-c.south) + (0.2, 0)$) --node[anchor=west, vulm]{$\omega_{c}$} ($(cnc.north) + (0.2, 0)$); - \draw[vulm, <-, double] ($(comp0-c.south) + (-0.2, 0)$) --node[anchor=east, vulm]{$\theta$} ($(cnc.north) + (-0.2, 0)$); - \draw[vulm, ->, double] ($(cnc) + (0.2, -1)$) -- node[anchor=west, vulm]{$\omega_{c}$} ($(cnc) + (0.2, -2)$);; - \draw[vulm, <-, double] ($(cnc) + (-0.2, -1)$) -- node[anchor=east, vulm]{$\theta$} ($(cnc) + (-0.2, -2)$);; - \draw[vulm, fill=yulm!30, rounded corners] ($(cnc) + (-2, -2)$) rectangle +(4, -1) node[pos=0.5] {Petite Machine à Bois}; - \end{tikzpicture} - \end{minipage} - \hfill - \begin{minipage}{.4\textwidth} - \begin{tikzpicture}[scale=.5] - \node[vulm, font=\small] (top) at (0, 0) {\parbox{.9\linewidth}{\textbf{Contrôleur:} traduit le GCode et envoie les vitesses de commande $\omega_{c}$ pour les moteurs.}}; - \node[vulm, font=\small] (mid) at ($(top.south) + (0, -2)$) {\parbox{.9\linewidth}{\textbf{Socket:} sert d'intermédiaire pour la connexion (PMB).}}; - \node[vulm, font=\small] (bot) at ($(mid.south) + (0, -2)$) {\parbox{.9\linewidth}{\textbf{Petite Machine à Bois:} envoie les positions $\theta$ de ses moteurs pas-à-pas.}}; - \node[draw=vulm, thick, fit={(top) (mid) (bot)}, inner sep=3pt, rounded corners] (box) {}; - \end{tikzpicture} - \end{minipage} - \hfill - \end{figure} -\end{frame} - - -\section{Implémentation}\label{sec:l'implementation} - -\begin{frame} - \frametitle{Traducteur} - On agit simplement ligne par ligne, voici quelques commandes: - \begin{center} - \begin{minipage}{.95\textwidth} - \begin{multicols}{2} - \begin{itemize} - \item[G0:] Aller au point donné; - \item[G1:] Aller au point donné, en coupant au passage; - \item[G2,G3:] Faire un arc de cercle, en sens trigonométrique ou non; - \item[G4:] Dormir; - \item[G5:] Suivre une courbe de Bézier cubique. Permet d'en suivre plusieurs d'affilée de manière dérivable; - \item[G20:] WTF IS A KILOMETER; - \item[G21:] Changer d'unité en mm; - \item[G28:] Retourner à la maison; - \item[G30:] Retourner à la maison, en passant par un point donné. - \end{itemize} -\end{multicols} -\end{minipage} -\end{center} -\end{frame} - -\begin{frame} - \frametitle{Contrôleur Matériel} - Le module matériel du contrôleur maintient une représentation mémoire de la position \emph{théorique} de la tête, transmise au contrôleur. - Il demande à la socket (et donc au \emph{vrai} matériel) les positions mesurées des moteurs pas-à-pas. - \begin{figure} - \centering - \begin{tikzpicture} - \draw[vulm, rounded corners, fill=yulm!30] (0, 0) rectangle +(2, 1) node[pos=0.5] (h) {Matériel}; - \draw[vulm, rounded corners, fill=yulm!30] (6, 0) rectangle +(2, 1) node[pos=0.5] (c) {Contrôleur}; - \draw[vulm, rounded corners, fill=yulm!30] (0, -2) rectangle +(2, 1) node[pos=0.5] (s) {Socket}; - \draw[vulm, ->] ($(h.east) + (.25, 0.1)$) -- node[above, vulm] {Position $\rho$ \& Cible $\tau$} ($(c.west) + (-0.1, 0.1)$); - \draw[vulm, <-] ($(h.east) + (.25, -0.1)$) -- node[below, vulm] {Vitesse $v_{c}$} ($(c.west) + (-0.1, -0.1)$); - \draw[vulm, ->] ($(h.south) + (.1, -.3)$) -- node[right, vulm] {$\omega_{c}$} ($(s.north) + (.1, 0.3)$); - \draw[vulm, <-] ($(h.south) + (-.1, -.3)$) -- node[left, vulm] {$\theta$} ($(s.north) + (-.1, 0.3)$); - \end{tikzpicture} - \end{figure} - Il envoie ensuite les vitesses de commandes traduites dans la spécification des moteurs. -\end{frame} - -\begin{frame} - \frametitle{Contrôleur Logiciel} - Dans une première approximation, on poursuit le point de contrôle à vitesse constante. - On calcule $\delta = \rho - \tau$. - Selon le signe de $\delta_{x}$ (resp. $y, z$) on définit $\omega_{c}$ la vitesse de commande de sorte que: - \begin{equation*} - \nu{\omega_{c}} = 1 \text{ et } \left(\omega_{c}\right)_{x} \leq \texttt{FAST\_XY\_FEEDRATE} \text{ (resp. $y, z$)} - \end{equation*} - Un contrôleur plus poussé est obtenu en ajoutant une proportionnalité par rapport à l'écart \emph{théorie - mesure}. -\end{frame} - -\begin{frame} - \frametitle{Simulateur} - Le simulateur tourne en continu, et ne change de vitesse pour résoudre les équations qu'à chaque changement de direction.\\ - On a ensuite par intégration directe du PFD: - \begin{align*} - J\dot{\omega} = \underset{\text{Friction}}{-f_{0} \times \omega_{t}} + \underset{\text{Gain Moteur}}{\Gamma \left(\omega_{c} - \omega_{t} \right)} \\ - \omega_{t + \Delta t} = \omega_{t} + \underset{\text{Inertie}}{J^{-1}} \times \Delta t \left( -f\omega + \Gamma\left( \omega_{c} - \omega_{t} \right) \right) - \end{align*} - On introduit par ailleurs un terme d'erreur lié aux frottements, et on peut encore raffiner le modèle. -\end{frame} - - -\section{Réalisme} - -\begin{frame} - \frametitle{Intérêt} - L'utilisation d'une socket et d'un modèle théorique restreint comme intermédiaire permet de prendre en compte, durant les phases de test, les différents facteurs d'erreurs pouvant intervenir durant l'exécution: - \begin{itemize} - \item Une connexion trop volatile; - \item Des erreurs liées au glissement des moteurs pas-à-pas; - \item Des erreurs liées à la dureté du matériau à couper, et à son anisotropie. - \end{itemize} - Ceci permet de plus à l'opérateur de modifier la trajectoire en direct pour rectifier/interrompre les opérations. -\end{frame} - -\begin{frame} - \frametitle{Réalisme de la Simulation} - \begin{itemize} - \item La simulation est pour l'instant peu réaliste car nos modèles physiques sont simplifiés. - \item Nos moteurs sont modélisés par des moteurs asynchrones plus que pas-à-pas. - \item Le contrôleur pour l'instant implémenté ne prend pas en compte la suite des instructions dans sa globalité, et en particulier ne prévoit pas de restreindre l'effort sur les moteurs, en revenant le plus possible à $\omega = 0$. - \end{itemize} -\end{frame} - -\end{document} diff --git a/svgtogcode.py b/svgtogcode.py index fffd298..e25e5e0 100644 --- a/svgtogcode.py +++ b/svgtogcode.py @@ -53,21 +53,6 @@ class SVGToGCodeConverter: gcode_lines.append(self.point_to_gcode(x, y)) return gcode_lines - # Following two are not canonical, I don't know where I found those lol - def ellipse_to_gcode(self, start: complex, end: complex, center: complex, rx: float, ry: float, rotation: float, clockwise: bool) -> str: - if "G7" not in self.supported_g_functions: - raise NotImplementedError("Ellipse support requires G7 function.") - i_offset = center.real - start.real - j_offset = center.imag - start.imag - g_command = "G7" # Assuming G7 is used for ellipses - return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} I{i_offset:.4f} J{j_offset:.4f} R1={rx:.4f} R2={ry:.4f} ROT={rotation:.4f}" - - def parabola_to_gcode(self, start: complex, vertex: complex, end: complex) -> str: - if "G6" not in self.supported_g_functions: - raise NotImplementedError("Parabola support requires G6 function.") - g_command = "G6" # Assuming G6 is used for parabolas - return f"{g_command} X{end.real:.4f} Y{end.imag:.4f} VERTEX_X{vertex.real:.4f} VERTEX_Y{vertex.imag:.4f}" - def wait_time_gcode(self, seconds: float) -> str: if "G4" not in self.supported_g_functions: raise NotImplementedError("Wait time support requires G4 function.") @@ -85,15 +70,7 @@ class SVGToGCodeConverter: elif isinstance(segment, svgpathtools.Arc): center = segment.center - if "G7" in self.supported_g_functions: - rx, ry = segment.radius.real, segment.radius.imag - rotation = segment.rotation - gcode.append(self.ellipse_to_gcode(segment.start, segment.end, center, rx, ry, rotation, segment.sweep_flag == 0)) - else: - gcode.append(self.arc_to_gcode(segment.start, segment.end, center, segment.sweep_flag == 0)) - - elif hasattr(segment, "vertex") and "G6" in self.supported_g_functions: - gcode.append(self.parabola_to_gcode(segment.start, segment.vertex, segment.end)) + gcode.append(self.arc_to_gcode(segment.start, segment.end, center, segment.sweep == 0)) else: raise ValueError(f"Unsupported path segment: {segment}") @@ -107,7 +84,7 @@ class SVGToGCodeConverter: file_path (str): Path to the input SVG file. output_path (str): Path to save the output G-code file. """ - paths, attributes = svgpathtools.svg2paths(file_path) + paths = extract_svg_paths(file_path) gcode = ["G21 ; Set units to mm", "G90 ; Absolute positioning"] # G-code header @@ -116,11 +93,31 @@ class SVGToGCodeConverter: start_point = path[0].start gcode.append(self.move_to_gcode(start_point.real, start_point.imag)) gcode.extend(self.parse_svg_to_gcode(path)) + gcode.extend("\n\n") with open(output_path, "w") as gcode_file: gcode_file.write("\n".join(gcode)) print(f"G-code saved to {output_path}") +# Function to extract and subdivide paths into continuous subpaths +def extract_svg_paths(svg_file): + paths, attributes = svgpathtools.svg2paths(svg_file) + continuous_paths = [] + + for path in paths: + subpath = [] + last_point = None + for seg in path: + if last_point is not None and seg.start != last_point: + continuous_paths.append(svgpathtools.Path(*subpath)) + subpath = [] + subpath.append(seg) + last_point = seg.end + if subpath: + continuous_paths.append(svgpathtools.Path(*subpath)) + + return continuous_paths + # Example usage: -# converter = SVGToGCodeConverter(["G1", "G2", "G3", "G5", "G6", "G7"]) -# converter.svg_to_gcode("example.svg", "output.gcode") +converter = SVGToGCodeConverter(["G1", "G2", "G3", "G4", "G5"]) +converter.svg_to_gcode("veldortokens.svg", "output.gcode")