diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..725d0ec --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [tomohiron907] +#open_collective: # Replace with a single Open Collective username +#patreon: # Replace with a single Patreon username +#ko_fi: # Replace with a single Ko-fi username +#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +#liberapay: # Replace with a single Liberapay username +#issuehunt: # Replace with a single IssueHunt username +#otechie: # Replace with a single Otechie username +#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index 16c5ba4..c66ff11 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ .DS_Store +.vscode/settings.json \ No newline at end of file diff --git a/README.md b/README.md index 0073a9d..eaafbc4 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,447 @@ + + # G-coordinator + +
+
+日本語バージョンは,このページの下に載せています. -日本語バージョンはQiitaで[ここ](https://qiita.com/tomohiron907/items/e14137fd15cb52a415dc)に載せています. # What is G-coordinator? -To use a 3D printer, it is basically necessary to prepare a 3D model, run it through slicing software to create a G-code, and load it into the printer. G-coordinator is an open source software for creating G-codes directly in python. +To use a 3D printer, basically, you need to prepare a 3D model and then slice it using slicing software to create G-code, which is then loaded into the printer. The G-coordinator developed this time is an open-source software in Python specifically designed for directly creating G-code. You can find it at the following URL: [https://github.com/tomohiron907/G-coordinator](https://github.com/tomohiron907/G-coordinator). + +Additionally, there is a Python library called "gcoordinator" that extracts the internal G-code generation engine of G-coordinator. (URL: https://github.com/tomohiron907/gcoordinator) + +Using this Python library, you can generate G-code directly from Python scripts without installing the GUI. The G-code generation engine inside the G-coordinator app also uses this Python library. + ![gif_img1](img/modeling.gif) -By creating G-codes directly, you can easily create shapes and forms that would be difficult to achieve using conventional methods of creating 3D models. For example, the following weave shape can be realized. +By directly creating G-code, it becomes possible to easily produce shapes and structures that were previously difficult to achieve using traditional methods of creating 3D models. For example, it allows for the realization of intricate knitting patterns like the one shown below. - -While Grasshopper in Rhinoceros can do the same thing with visual programming, G-coordinator uses python to achieve the same thing. This allows a high degree of freedom in modeling. -By drawing a python script in the editor on the left and executing it, the nozzle path of the 3D printer is displayed and a preview of the model can be checked. The left side of the screen allows you to adjust various print settings and output G-code. + + + + + + +
-# Requirements -To use G-coordinator, you need to have a good python environment. Some knowledge of python is also required for modeling. -In the following article, VS-CODE is used, but of course, pycharm and the like are also acceptable. -For the final check of the output G-cocde, software such as prusa-slicer or repetier would be useful. +
+ -# G-coordinator installation procedure -This section is intended for those who are not familiar with python, so those who are familiar with python can skip this section. -The source code of G-coordinator is available on github [here](https://github.com/tomohiron907/G-coordinator). (You can download the zip file from the bottom of the green <>code button.) The main code is placed in the src directory, so after downloading and extracting, open the src file with an editor. In this directory, 'main.py' is the main pytnon script to be executed. -Then, install the necessary libraries by pip. The necessary items are listed in requiremets.txt. -![img3](img/after_installation.png) +
+By writing Python scripts in the left editor and executing them, you can display the nozzle path of the 3D printer and preview the fabrication process. Additionally, you can adjust various printing settings on the right side of the screen and output the G-code. -Download the libraries needed to run the program by typing the following in a terminal. -```pip install numpy``` -```pip install pyqt5``` -```pip install pyqtgraph``` -```pip install pyopengl``` +# Requirements +G-coordinator currently supports macOS and Windows. Additionally, you can launch it by directly executing the Python file. In that case, you can install the required libraries in bulk using the following command: + +``` +pip install -r requirements.txt +``` -In some cases, you may need to install something that comes standard on a mac, but is not required on a win machine. -If the error disappears and main.py is executed, the installation is complete. +Please make sure you have Python and pip installed on your system before running the command. +
+When launching G-coordinator as an executable file like .app or .exe, you do not need to install the libraries separately. However, if you plan to examine the outputted G-code, it would be convenient to have software like Prusa Slicer or Repetier available. These software tools can help you visualize and analyze the G-code generated by G-coordinator. -![img4](img/launch_G-coordinator.png) +# G-coordinator installation procedure +Please download the software from [here](https://github.com/tomohiron907/G-coordinator/releases) according to your operating system. + +If you have a Python environment set up, please create a clone of this GitHub repository and set the current directory to "src." Then, execute the "main.py" file. The necessary INI configuration file for execution is also included in the "src" directory. +Furthermore, I have uploaded a video on YouTube that summarizes the installation process. You can watch it at the following link: https://www.youtube.com/watch?v=LqZGno-BWG0. # How G-code works -Before modeling, it is useful to understand the structure of a G-code briefly, so that you can model it in the future. The photo below shows a G-code opened in REPETIER. +It is useful to have at least a basic understanding of the structure of G-code before engaging in fabrication. The photo below depicts an example of G-code opened in Repetier: ![img5](img/reptier.png) ```G1 F800 X114.97987 Y105.63424 Z2.00000 E0.00589``` -These lines are repeated in large numbers. -This is an instruction to push the filament out 0.00589 mm from its current position to position X114.97987 Y105.63424 Z2.00000, moving at a speed of 800 mm per minute. +This is a command that is repeated multiple times in the G-code. It instructs the machine to move from its current position to the specified position of X114.97987, Y105.63424, and Z2.00000, while extruding filament at a rate of 0.00589mm per step, with a feedrate of 800mm per minute. This command is commonly used for controlled movements and filament extrusion during 3D printing or CNC machining processes. -In other words, when it comes right down to it, there are a total of five elements to be controlled by G-code: three elements of coordinates (x, y, z), speed, and the amount of extrusion. In addition, since speed and extrusion can be determined automatically by the G-coordinator (of course, they can also be specified individually), all that needs to be considered are the coordinates at which the nozzle moves. +In other words, when it comes down to it, there are five main elements that should be controlled using G-code: the three coordinates (x, y, z), speed, and extrusion amount. Additionally, the speed and extrusion can be automatically determined by the G-coordinator (though it is also possible to specify them individually). Therefore, the only thing you need to consider is the coordinate of the nozzle's movement, i.e., where the nozzle should move to. # Test modeling (cylinder) -Now, let's start modeling with G-coordinator. First, let's create a cylinder wall as the simplest model. -In the folder downloaded from github, there is a folder named "Example". Open 'default_cylinder.py' in the "example" folder by clicking the "open file" button in the upper left corner. +Alright, let's start creating the model using G-coordinator. First, as the simplest model, we will create the walls of a cylinder. Inside the downloaded folder from GitHub, you will find a folder called "Example." Click on the "Open File" button at the top left and open the 'default_cylinder.py' file located inside the 'Example' folder. The code will be displayed in the editor on the left, and when you press the reload button, it will look like the picture below. + ![img6](img/test_modeling.png) -In G-coordinator, modeling is done in a function called object_modeling(). As mentioned earlier, what we want is a list of coordinates. Therefore, we create a 3D list that contains the coordinates of the points to be passed through. - -The name "full_object" is the name of the 3D list, but the detailed structure of the list will be described in a separate article. -```ruby -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,100) - rad = 10 - x = rad*np.cos(arg) - y = rad*np.sin(arg) - z = np.full_like(arg, height*0.2+0.2) - layer = print_layer(x,y,z) - full_object.append(layer) - - - return full_object +In G-coordinator, the modeling process takes place within the function called `object_modeling()`. As mentioned earlier, what we ultimately want is a list of coordinates. Therefore, we are creating a list that includes the coordinates of the points that the tool should pass through. + + +```python +import math +import numpy as np +import gcoordinator as gc + +LAYER = 100 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, 2*np.pi, 100) + x = 10 * np.cos(arg) + y = 10 * np.sin(arg) + z = np.full_like(arg, (height+1) * 0.2) + wall = gc.Path(x, y, z) + full_object.append(wall) + +gc.gui_export(full_object) ``` -The following is a description of what is being done in the function. -First, each layer is iterated using a for statement. The current number of layers is 10, so the function iterates from layer 0 to layer 9. +In G-coordinator, the modeling process involves using objects of the Path class. This refers to the path along which the nozzle continuously extrudes resin. In the case of the cylindrical example mentioned earlier, the nozzle follows a circular path within a certain layer while continuously extruding resin. Therefore, the path of a single circle is represented by an object called "path." + +![img7](img/nozzle_path.png) + + +Let's break down what the function is doing. First, it iterates through each layer using a `for` loop. Since the current layer count is 100, it will iterate from layer 0 to layer 99. + +Next, to draw a circle, the function sets up an angle (`argument`) using a numpy array ranging from 0 to 2π. With an array size of 100, it will precisely form a regular 99-sided polygon. Assuming a fixed radius of 10, the x-coordinate can be calculated as `radius * cos(argument)`, and the y-coordinate as `radius * sin(argument)`. As for the z-coordinate in the vertical direction, it initializes an array with the same size as `argument` and assigns values based on the `height` variable. Adding 0.2 ensures that even if the height starts from 0, the first layer will be printed at a height of 0.2. -Next, to draw a circle, the angle (argument) is set in a numpy array in the range of 0 to 2π. The number of elements is set to 100, so exactly 99 regular angles are created. +
-If we fix the radius at 10, then -The x-coordinate is ```radius * cos(arg)``` -The y-coordinate is ```radius * sin(arg)```. -For the z-coordinate in the height direction, an array with the same number of elements as arg is initialized according to the height. 0.2 is added because we want the first layer to print at a height of 0.2, even if the height starts at 0. +```layer = gc.Path(x,y,z)``` -```layer = print_layer(x,y,z)``` +Furthermore, in the code, the endpoint of the path in the nth layer and the starting point of the path in the n+1st layer are automatically connected, allowing for smooth travel between layers. -This line is formatted to add an array of x-coordinates, an array of y-coordinates, and an array of z-coordinates to full_object. The content is similar to np.column_stack(), but F (speed) and E (coefficient of extrusion) can be added as arguments. We plan to write a separate article on this point. -The end point of the nth layer and the start point of the n+1st layer are automatically traveled. -For each layer, a layer is added to the list of full_objects and a full_object is set as the return value. +For each layer, the function appends the path to the `full_object` list and sets `full_object` as the return value. # Printing settings Once modeling is complete, prepare the G-code. -![img7](img/print_parameter.png) + + +At the current stage, the available printing settings are limited to the essential parameters. As the names suggest, `nozzle_diameter` refers to the diameter of the nozzle, and `layer_height` corresponds to the height of each layer. + +If you click on the "Machine Settings" button at the top, a window will appear where you can adjust the hardware settings of the 3D printer. + + + + In this section, you have the option to select the kinematics for your 3D printer. Generally, for most printers, you would choose the "Cartesian" option, which supports the three axes (x, y, z). + +However, there are additional options available to support specific types of 3D printers: + +1. "Nozzle Tilt": This option is for printers that have a robotic arm or a hexapod-like structure and require G-code specifically designed for these types of printers. + +2. "Bed Tilt": This option is for printers with a bed that can tilt, similar to a machining center. -At this stage, it is not possible to make such complicated print settings. Only the minimum settings are available. -As you can see, nozzle_diameter is the nozzle diameter and layer_height is the layer height. +3. "Bed Rotate": This option is for printers with a rotating bed. -The Origin item requires a bit of attention. In general 3D printers, the origin is set to the front left of the bed, but in G-coordinator, the origin is set to the center of the bed to make it easier to write the formula for modeling. -The bed of the 3D printer I am currently using is 210mm x 210mm, so I set the origin at 105mm from the center of the bed. +These options cater to printers with specialized kinematics beyond the standard Cartesian configuration. -Coordinates of (10,-20) on the G-coordinator are converted to (115, 85) on the G-code and recorded. +One important thing to note is the "Origin" setting. In most standard 3D printers, the origin is set at the front-left corner of the bed. However, in G-coordinator, the origin is set at the center of the bed to simplify the mathematical expressions used in modeling. +
+For example, if your printer bed is 210mm x 210mm in size, the origin is set at the center, which is 105mm in both the X and Y directions. +
+In G-coordinator, coordinates entered as (10, -20) would be converted and recorded as (115, 85) in the actual G-code, considering the shifted origin. -In addition, a speed setting is also provided. The print speed here is the default value when nothing is set, and if the speed is specified in detail in the editor, it takes precedence. The same is true for the bottom item Extrusion_multiplier. +Another setting available is the "Speed" option. The default value represents the print speed when no specific speed is set. However, if you specify the speed in detail within the editor, that value will take precedence. -In travel_option, you can set whether or not retraction is used and the z-hop. +The same applies to the "Extrusion Multiplier" option. If you define a specific value within the editor, it will override the default setting. -In extrusion_multiplier, you can determine the factor by which the extrusion amount (E value) is multiplied. +In the "Travel Option" section, you can configure options for retraction and Z-hop during travel movements. + +The "Extrusion Multiplier" setting allows you to determine a coefficient to scale the extrusion amount (E value). # Export G-code When ready, press the Gcode Export button. -When the message "Gcode Exported" appears in the message console in the lower left corner, the process is complete. -A file named G-coordinator.gcode is generated in the src directory of the current directory. -![img8](img/src_folder.png) + + +In this window, only the first 1000 lines of the generated G-code are displayed. However, when you click the "Save" button, the entire G-code will be saved. +
+
+When saving the G-code, make sure to enter the name with the extension. The format should be "{name}.gcode" for proper file saving. + + +
+ +# Lastly + +Using G-coordinator requires a combination of coding, mathematics, and an interest in 3D printing. Given that this combination may not be common among individuals, it can be challenging for people to start creating designs from scratch. As a result, the user base for G-coordinator may be limited. +

+To foster community engagement and encourage more users, it would be beneficial for individuals to actively tweet about their experiences using G-coordinator and share their creations or modifications under the hashtag #Gcoordinator (without a hyphen). This can help in promoting community interaction and participation. + +# Works + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ +--- +
+
+ +# G-coordinatorとは? +3Dプリンタを使用するためには,基本的には3Dモデルを用意し,それをスライスソフトにかけてG-codeを作成してプリンタに読み込ませる必要があります.今回開発したG-coordinatorはpythonで直接G-codeを作成するためのオープンソースフトウェアです.(URL:https://github.com/tomohiron907/G-coordinator) + +また,内部のG-code生成エンジンを切り出したgcoordinatorというライブラリもあります.(URL:https://github.com/tomohiron907/gcoordinator) + +このpythonライブラリをしようすると,GUIをインストールすることなく,pythonスクリプトからG-codeを生成することができます.このG-coordinatorのアプリも,内部のG-code生成エンジンはgcoordinatorを使用しています. + +![gif_img1](img/modeling.gif) + +
+ +直接G-codeを作成することにより,従来の3Dモデルを作成する方法では実現が困難であった形状や造形を,容易に作り出すことができます.例えば,下のような編み形状を実現できます. + + + + + + + + +
+ + +
+RhinocerosのGrasshopperではビジュアルプログラミングにて同様のことは可能ですが,G-coordinatorではそれをpythonで実現します. + +
+左側のエディタにpythonスクリプトを描き,それを実行することにより,3Dプリンタのノズルパスが表示され造形のプレビューが確認できます.また,画面の左側で各種印刷設定を調整し,G-codeを出力することができます. + +
+ + +# 必要事項 +G-coordinatorは現在,macOSとWindowsに対応しています. +また,pythonファイルを直接実行することでも起動できます. +その場合には,以下のコマンドでライブラリを一括インストールできます. +``` +pip install -r requirements.txt +``` +.appや.exeなどの実行ファイルとしてG-coordinatorを起動した場合にはライブラリのインストールは必要ありません. +出力されたG-codeを確認するために,Prusa slicerやReptierなどのソフトがあれば便利です. + + +# G-coordinatorのインストール +[ここ](https://github.com/tomohiron907/G-coordinator/releases)から,お使いのOSにあったものをダウンロードしてください. + + +python環境が整っている場合には,このgithubリポジトリのクローンを作成し,カレントディレクトリをsrcにして,main.pyを実行してください.実行に必要なini設定ファイルもsrcの中に入っているからです. + +また,インストール手順をまとめた動画もyoutube にアップしています. +https://www.youtube.com/watch?v=LqZGno-BWG0 + + +# G-codeの仕組み + +造形の前に,G-codeの構造を簡単にでも把握しておくことは,今後,造形を行う上でも有用です.下の写真は,repetierでG-codeを開いたものです -It is recommended to check the generated G-code with other software, just to be sure. ![img5](img/reptier.png) -These are the steps to create a G-code with G-coordinator. -We will update the examples and the scripts for them as needed. + + +
+ +基本的には, +```G1 F800 X114.97987 Y105.63424 Z2.00000 E0.00589``` +こういった行が大量に繰り返されています. +これは,現在の位置からX114.97987 Y105.63424 Z2.00000の位置まで,分速800mmで移動しながら,フィラメントを0.00589mm押し出すという命令です. + +つまり,突き詰めれば,G-codeで制御すべき要素は,座標(x, y, z)の三要素とスピード,押し出し量の計5つです.さらに,スピードと,押し出しはG-coordinator から自動で決定できる(もちろん細かく個別に指定することも可能)なので,考えるべきは,どの位置にノズルが動くかという座標のみで良いです. + +# テスト造形(円柱) +では,いよいよ,G-coordinatorで造形をしていきましょう.まずは,最も簡単なモデルとして,円柱の壁を作ります. +githubからダウンロードしたフォルダの中に,Exampleというフォルダがあります.左上のopen fileを押してexampleの中の’default_cylinder.py'を開いてください. + +左のエディタにコードが表示され,reloadボタンを押すと,以下の写真のようになります. + +![img6](img/test_modeling.png) + +
+ +G-coordinator では,object_modeling()という関数の中でモデリングを行います.先ほど述べた通り,最終的に欲しいものは,座標のリストです.なので,通るべき点の座標を含んだlistを作成しています. + + + +
+ +```python +import math +import numpy as np +import gcoordinator as gc + +LAYER = 100 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, 2*np.pi, 100) + x = 10 * np.cos(arg) + y = 10 * np.sin(arg) + z = np.full_like(arg, (height+1) * 0.2) + wall = gc.Path(x, y, z) + full_object.append(wall) + +gc.gui_export(full_object) +``` +
+ +G-coordinatorの中では,Pathクラスのオブジェクトをモデリングに使用します。 +これは,ノズルが樹脂を絶え間なく出し続ける経路のことを指しています。つまり,上の円柱の例では,ノズルは,ある層において,樹脂を出し続けながら,円を描くので一つの円の経路がpathというオブジェクトです. + +
+ +![img7](img/nozzle_path.png) + +
+関数内で何をしているかについてです. +まず,for文で各レイヤーについて繰り返しをしています.現在のレイヤー数は100なので,0層目から99層目まで繰り返されるイメージです. + +
+次に,円を描くために,角度(argument)をnumpy arrayで0から2πの範囲で設定しています.要素数は100としているため,正確には,正99角形が造形されます. +半径は10で固定すると, +x座標は, + +```半径×cos(arg)``` +y座標は, +```半径×sin(arg)``` +より計算できます. +高さ方向のz座標に関しては,argと同じ要素数のarrayをheightに応じて値を初期化しています.0.2を足しているのは,heightが0から始まっても,第一層目は高さ0.2の場所に印刷して欲しいからです. + +```layer = gc.Path(x,y,z)``` + +
+なお,n段目のPathの終点とn+1段目のPathの始点とは,自動でトラベルするようになっています. +そして,各レイヤーごとに,full_objectのlist にPathを追加し,full_objectを返り値として設定しています. + +
+ +# 印刷設定 + +造形が完了すれば,G-codeの準備をします. + + + +現段階では,そこまで,複雑な印刷設定ができないです.最低限の設定項目のみです. +読んで字のごとくですが,nozzle_daimeterはノズル径,layer_heightはレイヤーの高さです. + +一番上のmachine settingsのボタンを押すと,3Dプリンタのハードウェア設定のウィンドウが出てきます. + +
+ + +ここでは,kinematicsを選択することが可能です.一般的には3軸x, y, zのCartesianの項目を選択してください.ここでは,他に, +- ロボットアーム型やHexaといった3Dプリンタ用のGcodeをサポートするために,Nozzle Tilt +- マシニングセンタのようなベッドが傾く3Dプリンタ用にBed Tilt +- ベッドが回転する3Dプリンタに用にBed Rotate + +がサポートされています。 + +
+少し注意の必要なのは,Origin の項目です.一般の3Dプリンタでは,原点をベッドの左手前に設定していますが,G-coordinatorでは,造形の数式を簡単に書くために,原点をベッドの中央に設定しています. +自分の今使用している3Dプリンタのベッドが210mm×210mmなので,その中心の105mm を原点と設定しています. + +
+G-coordinator上で(10,-20)の座標が,G-code上では(115, 85)に変換されて記録されるイメージです. + +
+他には,スピードの項目も設けています.ここでのプリントスピードは,何も設定しなかった場合のデフォルト値であり,エディタで細かくスピードを指定した場合には,そちらが優先されます.これは,一番下の項目のExtrusion_multiplierでも同様です. + +
+travel_optionでは,リトラクションの有無とzホップの設定が可能です. + +
+extrusion_multiplier では,押し出し量(E値)に掛ける係数を決定できます. + + +# G-codeの出力 + +準備が整ったら,Export Gcodeボタンを押してください + + + +このウィンドウでは,G-code作成したG-codeの最初の1000行だけが表示されています。もちろん,保存ボタンを押して保存されるのはG-code全体です.また,Gーcodeを保存するときに,名前は,{名前}.gcodeと、拡張子まで入力する必要があります。 + +
+ +# 最後に + +G-coodinatorを使うにあたり,造形のためのコードや数学と3Dプリンタの両方に興味のある人が少ないこともあり,なかなか自分で1から造形をおこなうのは難しく,ユーザも限られてしまいます.そこで,G-coordinatorで造形をおこなったり,改造して印刷をおこなったりしたものを積極的に #Gcoordinator(ハイフンなしに注意)でツイートしてもらえると,よりコミュニティの活性化につながると思っています. + +# 作品 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/doc/G-coordinator_presentation.pdf b/doc/G-coordinator_presentation.pdf new file mode 100644 index 0000000..b4b2fdd Binary files /dev/null and b/doc/G-coordinator_presentation.pdf differ diff --git a/doc/G-coordinator_presentation.pptx b/doc/G-coordinator_presentation.pptx new file mode 100644 index 0000000..dfb8e3f Binary files /dev/null and b/doc/G-coordinator_presentation.pptx differ diff --git a/example/default_cylinder.py b/example/default_cylinder.py deleted file mode 100644 index 637985b..0000000 --- a/example/default_cylinder.py +++ /dev/null @@ -1,27 +0,0 @@ -import numpy as np -import math - -from print_functions import * -LAYER=50 - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,100) - rad = 10 - x = rad*np.cos(arg) - y = rad*np.sin(arg) - z = np.full_like(arg, height*0.2+0.2) - layer = print_layer(x,y,z) - full_object.append(layer) - - - - - - - - return full_object - diff --git a/example/envelope_line.py b/example/envelope_line.py deleted file mode 100644 index ba6e20f..0000000 --- a/example/envelope_line.py +++ /dev/null @@ -1,77 +0,0 @@ -import numpy as np -import math -from print_functions import * -LAYER=100 -d = 0.8 -gr = 1/1.618 - - - -''' -NoZZLE = 0.4 -LAYER = 0.2 -speed = 650 -ext = 1 -''' -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(np.pi/4, np.pi*9/4,5) - rad = 40*math.sqrt(2) - x = rad*np.cos(arg) - y = gr*rad*np.sin(arg) - z = np.full_like(arg, height*0.2+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - rad = 40.4*math.sqrt(2) - x = rad*np.cos(arg) - y = gr*rad*np.sin(arg) - z = np.full_like(arg, height*0.2+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - - x = np.linspace(-40,40,10) - y = gr*np.linspace(40,-40,10) - z = np.full_like(x, height * 0.2 + 0.2) - - layer = print_layer(x, y , z) - full_object.append(layer) - - - if height != 0 and height %3 ==0: - x = np.linspace(40, 40-d*height,10) - y = gr*np.linspace(40-d*height,-40+d*height,10) - z = np.full_like(x,height*0.2+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - - - - - - x = np.linspace(-40, -40+d*height,10) - y = gr*np.linspace(-40+d*height,+40-d*height,10) - z = np.full_like(x,height*0.2+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - if height != 0 and height %3 ==1: - x = np.linspace(40, 40-d*(height-1),10) - y = gr*np.linspace(40-d*(height-1),-40+d*(height-1),10) - z = np.full_like(x,(height)*0.2+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - x = np.linspace(-40, -40+d*(height-1),10) - y = gr*np.linspace(-40+d*(height-1),+40-d*(height-1),10) - z = np.full_like(x,(height)*0.2+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - - - return full_object - diff --git a/example/hyperboloid.py b/example/hyperboloid.py deleted file mode 100644 index 82f34b9..0000000 --- a/example/hyperboloid.py +++ /dev/null @@ -1,64 +0,0 @@ -import numpy as np -import math -from print_functions import * - -LAYER=250 - -def rotation(height): - t = height / LAYER - angle = math.atan(t/(1-t)) - return angle - - -def amp_calc(height): - if height > LAYER * 0.94: - amp = 4 * math.cos((height-0.94 * LAYER)/ (0.06 * LAYER) * np.pi / 2) - elif height < LAYER * 0.06 : - amp = 4 * math.sin((height)/ (0.06 * LAYER) * np.pi / 2) - - else : - amp = 4 - return amp - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,401) - amp = amp_calc(height) - rad = [40-30*1.5*(height/LAYER)+30*1.5*(height/LAYER)**2+amp*math.floor(abs(1.2*math.sin(arg_i*25))) for arg_i in arg] - - angle = rotation(height) - x = rad*np.cos(arg+angle) - y = rad*np.sin(arg+angle) - z = np.full_like(arg, height*0.3+0.2) - layer_pos = np.column_stack([x, y, z]) - outer_wall = print_layer(x, y, z) - full_object.append(outer_wall) - - - - - - - - for i in range(2): - arg = np.linspace(0, np.pi*2,201) - rad = [40-30*1.5*(height/LAYER)+30*1.5*(height/LAYER)**2 - 0.6 for arg_i in arg] - rad = [ rad_i-0.6*i for rad_i in rad] - angle = rotation(height) - x = rad*np.cos(arg+angle) - y = rad*np.sin(arg+angle) - z = np.full_like(arg, height*0.3+0.2) - inner_wall = print_layer(x, y, z) - full_object.append(inner_wall) - - - - - - - - return full_object - - diff --git a/example/lidded_cylinder.py b/example/lidded_cylinder.py deleted file mode 100644 index e9e63f3..0000000 --- a/example/lidded_cylinder.py +++ /dev/null @@ -1,34 +0,0 @@ -import numpy as np -import math -from print_functions import * -LAYER=10 - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,100) - rad = [10 for arg_i in arg] - x = rad*np.cos(arg) - y = rad*np.sin(arg) - z = np.full_like(arg, height*0.4+0.4) - layer = print_layer(x,y,z) - full_object.append(layer) - - for polar in range(12): - arg = np.linspace(0, np.pi*2,100) - rad = 10-0.76-polar *0.76 - x = rad*np.cos(arg) - y = rad*np.sin(arg) - z = np.full_like(arg, LAYER*0.4) - layer = print_layer(x,y,z,Feed = 300) - full_object.append(layer) - - - - - - - return full_object - diff --git a/example/polygon_case.py b/example/polygon_case.py deleted file mode 100644 index e1a0123..0000000 --- a/example/polygon_case.py +++ /dev/null @@ -1,37 +0,0 @@ -import numpy as np -import math -from print_functions import * - -LAYER=50 - - - -def object_modeling(): - pos_array=[] - full_object = [] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,7) - rad = 25 - x = rad*np.cos(arg) - y = rad*np.sin(arg) - z = np.full_like(arg, height*0.2+0.2) - wall = print_layer(x, y, z) - full_object.append(wall) - if height==0: - rad=25 - x = (rad-0.4)*np.cos(arg) - y = (rad-0.4)*np.sin(arg) - z = np.full_like(arg, height*0.2+0.2) - layer_pos = np.column_stack([x, y, z]) - infill_pos = line_fill(layer_pos,0.4,0) - bottom = print_layer(infill_pos[0], infill_pos[1], infill_pos[2]) - full_object.append(bottom) - - - - - - - - return full_object - diff --git a/example/rectangle_wall.py b/example/rectangle_wall.py deleted file mode 100644 index e09b550..0000000 --- a/example/rectangle_wall.py +++ /dev/null @@ -1,28 +0,0 @@ -import numpy as np -import math -from print_functions import * -LAYER=20 - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - '''arg = np.linspace(0, np.pi*2,5) - rad = [10 for arg_i in arg] - x = rad*np.cos(arg) - y = rad*np.sin(arg) - z = np.full_like(arg, height*0.3+0.2) - F = np.full_like(x, 400) - E = np.full_like(x,0) - layer = print_layer(x, y, z,F,E) - full_object.append(layer)''' - - x = np.array([10,-10,-10,10,10],dtype = float) - y = np.array([10,10,-10,-10,10],dtype = float) - z = np.full_like(x, height*0.3+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - return full_object - diff --git a/example/twist_cup.py b/example/twist_cup.py deleted file mode 100644 index d860ca5..0000000 --- a/example/twist_cup.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np -import math -from print_functions import * - -LAYER=380 -def rotation_calc(height): - x=height/LAYER - rotation=math.sin((x)*2.5*np.pi) - return rotation*0.3 - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg=np.linspace(0,2*np.pi,481) - rad=22+ 4*np.floor(1.05*abs(np.sin(arg*40))) - if height/LAYER<=0.25: - rad=rad-5*(1-np.sin((height/LAYER)/0.25*np.pi/2)) - rotation = rotation_calc(height) - - x = rad*np.cos(arg+rotation) - y = rad*np.sin(arg+rotation) - z = np.full_like(arg, height*0.2+0.2) - wall = print_layer(x, y, z) - full_object.append(wall) - - - - - # _c means inner wall - arg_c=np.linspace(0,-2*np.pi,201) - rad_c=22-0.25 - if height/LAYER<=0.25: - rad_c = rad_c-5*(1-np.sin((height/LAYER)/0.25*np.pi/2)) - rotation = rotation_calc(height) - x_c = rad_c*np.cos(arg_c+rotation) - y_c = rad_c*np.sin(arg_c+rotation) - z_c = np.full_like(arg_c, height*0.2+0.2) - wall_c = print_layer(x_c, y_c, z_c) - full_object.append(wall_c) - - - - - return full_object diff --git a/example/wave_bowl.py b/example/wave_bowl.py deleted file mode 100644 index 2667320..0000000 --- a/example/wave_bowl.py +++ /dev/null @@ -1,63 +0,0 @@ -import numpy as np -import math -from print_functions import * - - -''' -NOZZLE = 0.8 -LAYER = 0.4 -Print_speed = 700 -Ext_multiplier = 2.6 -''' - -LAYER=100 -base_rad = 50 - - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,203) - amp = 2 - rad = base_rad+amp*np.sin(arg*50.5+np.pi*height) + 10*np.sin((height+arg/(2*np.pi))/LAYER*np.pi*2) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.linspace(height*0.7,(height+1)*0.7,203) - wave_wall = print_layer(x, y, z) - full_object.append(wave_wall) - - if height <2: - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-2 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*0.7+0.7) - #F = np.full_like(arg,500) - inner_wall = print_layer(x, y, z) - full_object.append(inner_wall) - - - - - - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-2-1 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*0.7+0.7) - - layer_pos = np.column_stack([x, y, z]) - infill = line_fill(layer_pos,1,np.pi/4*(-1)**height) - bottom = print_layer(infill[0], infill[1], infill[2]) - full_object.append(bottom) - - - - - - return full_object - diff --git a/example/wave_case.py b/example/wave_case.py deleted file mode 100644 index 7626718..0000000 --- a/example/wave_case.py +++ /dev/null @@ -1,72 +0,0 @@ -import numpy as np -import math -from print_functions import * - - -''' -NOZZLE = 1 -LAYER = 1 -Print_speed = 500 -Ext_multiplier = 1 -''' - -LAYER=90 -base_rad = 35 - - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,143) - amp = 3 - z = np.linspace(height*1,(height+1)*1,143) - rad = base_rad+amp*np.sin(arg*35.5+height*np.pi) - #rad += 7*np.sin((height/LAYER)*np.pi) - x = rad*np.cos(arg )+7*np.sin(z/LAYER*2*np.pi*3.2) - - y = rad*np.sin(arg ) - - wave_wall = print_layer(x, y, z) - full_object.append(wave_wall) - - if height <2: - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-3 for arg_i in arg] - #rotation = rotation_calc(height) - z = np.full_like(arg, height*1+1) - x = rad*np.cos(arg )+7*np.sin(z/LAYER*2*np.pi*3.2) - y = rad*np.sin(arg ) - - #F = np.full_like(arg,500) - inner_wall = print_layer(x, y, z,Feed = 1000) - full_object.append(inner_wall) - - - - - - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-3-0.5 for arg_i in arg] - #rotation = rotation_calc(height) - z = np.full_like(arg, height*1+1) - x = rad*np.cos(arg )+7*np.sin(z/LAYER*2*np.pi*3.2) - y = rad*np.sin(arg ) - - - layer_pos = np.column_stack([x, y, z]) - infill = line_fill(layer_pos,2.5,np.pi/4*(-1)**height) - bottom = print_layer(infill[0], infill[1], infill[2],Feed = 1000) - full_object.append(bottom) - - - travel_point = travel_to(0,0,10) - full_object.append(travel_point) - - - - - - return full_object - diff --git a/example/wave_container.py b/example/wave_container.py deleted file mode 100644 index 8d86d89..0000000 --- a/example/wave_container.py +++ /dev/null @@ -1,77 +0,0 @@ -import numpy as np -import math -import parameter_curve_func as pf -from print_functions import * -''' -NOZZLE = 0.8 -LAYER = 0.7 -Print_speed = 700 -Ext_multiplier = 1.4 -''' - -LAYER=120 - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - if height <2: - arg = np.linspace(0, np.pi*2,401) - rad = [30-1 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*0.7+0.7) - #F = np.full_like(arg,500) - - layer = print_layer(x, y, z) - full_object.append(layer) - - - - arg = np.linspace(0, np.pi*2,401) - rad = [30-1-1+height*0.4 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*0.7+0.7) - #layer_pos = np.column_stack([x, y, z]) - F = np.full_like(arg,15) - - outer_circle = np.column_stack([x, y, z]) - fill = line_fill(outer_circle,2.4,np.pi/4*(-1)**height) - - fill_layer = print_layer(fill[0],fill[1],fill[2]) - full_object.append(fill_layer) - - - full_object.append([[0,0,10]]) - - - - - t = np.linspace(0,1,203) - arg = t * 2*np.pi - height_list = (height + t)/LAYER - - bezier_rad =np.array( [30 * pf.function(height_list_i/1.01) for height_list_i in height_list]) - amp = 1+4.5*np.exp(-(12*(height/LAYER-1/2.8))**2) - rad=30+amp*np.sin(arg*50.5+np.pi*height)+ bezier_rad - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.linspace(height*0.7,(height+1)*0.7,203) - layer = print_layer(x, y, z) - full_object.append(layer) - - - - - - - - - - - return full_object - diff --git a/example/wave_cylinder.py b/example/wave_cylinder.py deleted file mode 100644 index 70e9553..0000000 --- a/example/wave_cylinder.py +++ /dev/null @@ -1,63 +0,0 @@ -import numpy as np -import math -from print_functions import * -import parameter_curve_func as pf - -''' -NOZZLE = 0.8 -LAYER = 0.4 -Print_speed = 700 -Ext_multiplier = 2.6 -''' - -LAYER=100 -base_rad = 50 - - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,203) - amp = 2 - rad = base_rad+amp*np.sin(arg*50.5+np.pi*height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.linspace(height*0.7,(height+1)*0.7,203) - wave_wall = print_layer(x, y, z) - full_object.append(wave_wall) - - if height <2: - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-2 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*0.7+0.7) - #F = np.full_like(arg,500) - inner_wall = print_layer(x, y, z) - full_object.append(inner_wall) - - - - - - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-2-1 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*0.7+0.7) - - layer_pos = np.column_stack([x, y, z]) - infill = line_fill(layer_pos,1.6,np.pi/4*(-1)**height) - bottom = print_layer(infill[0], infill[1], infill[2]) - full_object.append(bottom) - - - - - - return full_object - diff --git a/example/wave_tray.py b/example/wave_tray.py deleted file mode 100644 index 2c31024..0000000 --- a/example/wave_tray.py +++ /dev/null @@ -1,71 +0,0 @@ -import numpy as np -import math -from print_functions import * - - -''' -NOZZLE = 1 -LAYER = 1 -Print_speed = 500 -Ext_multiplier = 1 -''' - -LAYER=40 -base_rad = 60 - - - - -def object_modeling(): - full_object=[] - for height in range(LAYER): - arg = np.linspace(0, np.pi*2,403) - amp = 2 - rad = base_rad+amp*np.sin(arg*100.5+np.pi*height) - rad += 7*np.sin((height/LAYER)*np.pi) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.linspace(height*1,(height+1)*1,403) - wave_wall = print_layer(x, y, z) - full_object.append(wave_wall) - - if height <2: - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-2 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*1+1) - #F = np.full_like(arg,500) - inner_wall = print_layer(x, y, z,Feed = 1000) - full_object.append(inner_wall) - - - - - - arg = np.linspace(0, np.pi*2,401) - rad = [base_rad-2-0.5 for arg_i in arg] - #rotation = rotation_calc(height) - x = rad*np.cos(arg ) - y = rad*np.sin(arg ) - z = np.full_like(arg, height*1+1) - - layer_pos = np.column_stack([x, y, z]) - infill = line_fill(layer_pos,2.5,np.pi/4*(-1)**height) - bottom = print_layer(infill[0], infill[1], infill[2],Feed = 1000) - full_object.append(bottom) - - x = 0 - y = 0 - z = 10 - - travel_point = print_layer(x, y, z) - full_object.append(travel_point) - - - - - - return full_object - diff --git a/example/wave_wall_circle.py b/example/wave_wall_circle.py deleted file mode 100644 index 5a731f1..0000000 --- a/example/wave_wall_circle.py +++ /dev/null @@ -1,52 +0,0 @@ -import numpy as np -import math -from print_functions import * - - -''' -NOZZLE = 0.4 -layer height = 0.2 -''' - - -LAYER=500 - -def function(height ,i): - value = 4 * math.cos(math.sqrt((i*24-12)**2+(height/LAYER*24-12)**2))*math.exp(-((i*24-12)/10)**2-((height/LAYER*24-12)/10)**2) - return value - -def object_modeling(): - full_object=[] - for height in range(LAYER): - t = np.linspace(0,1,200) - x = t*100-50 - #y = [2 * math.sin(i*2*np.pi*3) * 2*math.sin((height/LAYER)*2*np.pi*4) for i in t] - y = [-6*math.floor( abs(1.05 * math.sin(i * 2 * np.pi * 20))) + function(height,i) for i in t] - z = np.full_like(t, height*0.2+0.2) - layer = print_layer(x, y, z) - full_object.append(layer) - - - t = np.linspace(0,1,200) - x = t*100-50 - y=[0.4+function(height,i) for i in t] - z = np.full_like(t, height*0.2+0.2) - wall = print_layer(x, y, z) - full_object.append(wall) - y_2=[0.8+function(height,i) for i in t] - wall_2 = print_layer(x, y_2, z) - full_object.append(wall_2) - - - - - - - - - - - - - return full_object - diff --git a/example/woven_wall.py b/example/woven_wall.py deleted file mode 100644 index bd0bbde..0000000 --- a/example/woven_wall.py +++ /dev/null @@ -1,72 +0,0 @@ -import numpy as np -import math -from print_functions import * -LAYER=20 - -def print_rectangle (height): - x = np.array([50,-50,-50,50,50],dtype = float) - y = np.array([25,25,-25,-25,25],dtype = float) - z = np.full_like(x, height*0.2+0.2) - layer = print_layer(x, y, z) - coords = np.column_stack([x, y, z]) - return layer - -def print_line(start_x, start_y, end_x, end_y,height): - x = np.linspace(start_x, end_x, 30) - y = np.linspace(start_y, end_y, 30) - z = np.full_like(x, height*0.2+0.2) - layer = print_layer(x, y, z, Feed = 400, E_multiplier = 2) - return layer - -def object_modeling(): - full_object=[] - for height in range(LAYER): - rectangle= print_rectangle(height) - full_object.append(rectangle) - offset = contour_offset(rectangle,0.4) - offset_line = print_layer(offset[0], offset[1], offset[2]) - full_object.append(offset_line) - - for i in range(10): - line = print_line(-50,25-i*5, -50+i*5, 25,20) - full_object.append(line) - for i in range(10): - line = print_line(-50+i*5,-25, i*5, 25,20) - full_object.append(line) - for i in range(10): - line = print_line(i*5,-25, 50, 25-i*5,20) - full_object.append(line) - - for i in range(10): - line = print_line(-50,-25+i*5, -50+i*5, -25,20) - full_object.append(line) - for i in range(10): - line = print_line(-50+i*5,25, i*5, -25,20) - full_object.append(line) - for i in range(10): - line = print_line(i*5,25, 50, -25+i*5,20) - full_object.append(line) - - - rectangle_top = print_rectangle(20) - full_object.append(rectangle_top) - - for i in range(20): - y = np.linspace(25,-25,100) - x = np.full_like(y, 5*i-50) - z = np.full_like(y, 4.4) - line = print_layer(x, y, z, Feed = 200, E_multiplier = 10) - full_object.append(line) - - for i in range(10): - x = np.linspace(-50,50,100) - y = np.full_like(x, 5*i-25+2.5) - z = np.full_like(x, 4.4) - line = print_layer(x, y, z, Feed = 200, E_multiplier = 10) - full_object.append(line) - - rectangle_top = print_rectangle(22) - full_object.append(rectangle_top) - - return full_object - diff --git a/examples/Hilbert_curve.py b/examples/Hilbert_curve.py new file mode 100644 index 0000000..0126034 --- /dev/null +++ b/examples/Hilbert_curve.py @@ -0,0 +1,37 @@ +import numpy as np +import gcoordinator as gc + +def hilbert_curve(x, y, xi, xj, yi, yj, depth): + if depth <= 0: + return [x + (xi + yi) / 2], [y + (xj + yj) / 2] + + xn = x + (xi + yi) / 2 + yn = y + (xj + yj) / 2 + + x1, y1 = hilbert_curve(x, y, yi / 2, yj / 2, xi / 2, xj / 2, depth - 1) + x2, y2 = hilbert_curve(x + xi / 2, y + xj / 2, xi / 2, xj / 2, yi / 2, yj / 2, depth - 1) + x3, y3 = hilbert_curve(x + xi / 2 + yi / 2, y + xj / 2 + yj / 2, xi / 2, xj / 2, yi / 2, yj / 2, depth - 1) + x4, y4 = hilbert_curve(x + xi / 2 + yi, y + xj / 2 + yj, -yi / 2, -yj / 2, -xi / 2, -xj / 2, depth - 1) + + return x1 + x2 + x3 + x4, y1 + y2 + y3 + y4 + +def draw_hilbert_curve(length, depth, height): + x = 0 + y = 0 + xi = length + xj = 0 + yi = 0 + yj = length + + x, y = hilbert_curve(x, y, xi, xj, yi, yj, depth) + z = np.full_like(x, 0.2*height + 0.2) + hilbert = gc.Path(x, y, z) + return hilbert + +full_object=[] +for height in range(10): + hilbert = draw_hilbert_curve(200, 6, height * 0.2) + hilbert = gc.Transform.move(hilbert, -100, -100) + full_object.append(hilbert) + +gc.gui_export(full_object) diff --git a/examples/Lissajous_curve_table.py b/examples/Lissajous_curve_table.py new file mode 100644 index 0000000..623f01b --- /dev/null +++ b/examples/Lissajous_curve_table.py @@ -0,0 +1,33 @@ +import numpy as np +import gcoordinator as gc + +LAYER =20 + +def lissajous(a, b, d, height): + t = np.linspace(0, np.pi* 2,400) + rad = 16 + gcd = math.gcd(a, b) + if gcd >1: + a_2=a/gcd + b_2=b/gcd + else: + a_2 = a + b_2 = b + x = rad*np.sin(a_2*t + d)+a*40-20-100 + y = rad*np.sin(b_2*t)+b*40-20-100 + z = np.full_like(t, (height+1)*0.2) + + wall = gc.Path(x, y, z) + if height == 0: + wall.z_hop = True + return wall + +full_object=[] +for height in range(LAYER): + for i in range(0, 5): + for j in range(0, 5): + curve = lissajous(i+1, j+1, np.pi/2.86*(i+1)*(j+1), height) + full_object.append(curve) + + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/brick_circle.py b/examples/brick_circle.py new file mode 100755 index 0000000..22744c6 --- /dev/null +++ b/examples/brick_circle.py @@ -0,0 +1,52 @@ +import numpy as np +import math +import gcoordinator as gc + +LAYER=500 +nozzle = 0.4 +thickness = 0.2 + +N = 40 + +def brick(center): + center_x = center[0] + center_y = center[1] + center_z = center[2] + rotation = np.arctan2(center_y, center_x) + brick_x = 4 + brick_y = 2 + brick_matrix = np.array([[-brick_x, brick_y], [brick_x, brick_y], [brick_x, -brick_y], [-brick_x, -brick_y], [-brick_x, brick_y]]) + rotation_matrix = np.array([[np.cos(-rotation), -np.sin(-rotation)], [np.sin(-rotation), np.cos(-rotation)]]) + brick_matrix = np.dot(brick_matrix, rotation_matrix) + x = brick_matrix[:, 0] + center_x + y = brick_matrix[:, 1] + center_y + z = np.array([center_z]*5) + wall = gc.Path(x, y, z) + + return wall + +full_object=[] +for height in range(LAYER): + if height%50<25: + phase = 0 + else: + phase = np.pi*2/N/2 + + t = np.linspace(0, 2*np.pi, N) + rad = 40 + x = rad * np.cos(t+phase) + y = rad * np.sin(t+phase) + z = np.full_like(x, thickness * (height+1)-0.1) + for i in range(N): + center = [x[i], y[i], z[i]] + wall = brick(center) + full_object.append(wall) + + if height<4: + circle = gc.Path(x, y, z) + circle = gc.Transform.offset(circle, -3) + infill = gc.line_infill(circle, infill_distance=1, angle=np.pi/4+np.pi/2*height) + full_object.append(infill) + + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/clock_case.py b/examples/clock_case.py new file mode 100644 index 0000000..4f0c40c --- /dev/null +++ b/examples/clock_case.py @@ -0,0 +1,77 @@ +import numpy as np +import math +import gcoordinator as gc + + +LAYER=56 +nozzle = 0.4 +thickness = 0.2 + +depth = 35 + + + +def quarter_func(arg, L): + a = 4 + rad = L * (np.cos(arg)**a + np.sin(arg)**a)**(-1/a) + return rad + + + +full_object=[] +for height in range(LAYER): + t = np.linspace(0, 2*np.pi, 200) + rad = quarter_func(t, 40) + x = rad * np.cos(t) + y = rad * np.sin(t) + z = np.full_like(x, thickness * (height +1)) + wall = gc.Path(x, y, z) + full_object.append(wall) + outer_wall = gc.Transform.offset(wall, 0.4) + full_object.append(outer_wall) + + if height<6: + x = 4.4 * np.cos(t) + y = 4.4 * np.sin(t) + z = np.full_like(x, thickness * (height +1)) + hole = gc.Path(x, y, z) + full_object.append(hole) + inner_hole = gc.Transform.offset(hole, -0.4) + full_object.append(inner_hole) + + contour = gc.PathList([wall, hole]) + infill = gc.line_infill(contour, infill_distance = 0.5, angle = np.pi/4 + np.pi/2*height) + infill.z_hop = True + infill.retraction = True + full_object.append(infill) + elif height>=6 and height<57: + + t = np.linspace(0, 2*np.pi, 1000) + L = 30 - 1.2-0.4+np.sqrt(10**2 - (height*0.2-10)**2) + rad = quarter_func(t, L) + if height<11: + rad -= 2*np.floor(1.003*abs(np.cos(t*6))) + x = rad * np.cos(t) + y = rad * np.sin(t) + z = np.full_like(x, thickness * (height +1)) + scale = gc.Path(x, y, z) + full_object.append(scale) + + + t = np.linspace(0, 2*np.pi, 200) + L = 30 - 1.2+np.sqrt(10**2 - (height*0.2-10)**2) + rad = quarter_func(t, L) + x = rad * np.cos(t) + y = rad * np.sin(t) + z = np.full_like(x, thickness * (height +1)) + wall = gc.Path(x, y, z) + full_object.append(wall) + outer_wall = gc.Transform.offset(wall, 0.4) + full_object.append(outer_wall) + outer_wall = gc.Transform.offset(outer_wall, 0.4) + full_object.append(outer_wall) + +gc.gui_export(full_object) + + + diff --git a/examples/cylinder_case.py b/examples/cylinder_case.py new file mode 100644 index 0000000..16f1a4a --- /dev/null +++ b/examples/cylinder_case.py @@ -0,0 +1,26 @@ +import numpy as np +import gcoordinator as gc + +LAYER =50 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,100) + rad = 10 + x = rad*np.cos(arg) + y = rad*np.sin(arg) + z = np.full_like(arg, height*0.2+0.2) + wall = gc.Path(x, y, z) + outer_wall = gc.Transform.offset(wall, 0.4) + full_object.append(wall) + full_object.append(outer_wall) + + if height <2 : + bottom = gc.line_infill(wall, infill_distance = 0.4, angle = np.pi/4 + np.pi/2*height) + #bottom = gc.Transform.rotate_xy(bottom, np.pi/2*height) + bottom.print_speed = 400 + bottom.z_hop = True + bottom.retraction = True + full_object.append(bottom) + +gc.gui_export(full_object) diff --git a/examples/default_cylinder.py b/examples/default_cylinder.py new file mode 100644 index 0000000..7d2a68b --- /dev/null +++ b/examples/default_cylinder.py @@ -0,0 +1,15 @@ +import math +import numpy as np +import gcoordinator as gc + +LAYER = 100 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, 2*np.pi, 100) + x = 10 * np.cos(arg) + y = 10 * np.sin(arg) + z = np.full_like(arg, (height+1) * 0.2) + wall = gc.Path(x, y, z) + full_object.append(wall) +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/double_pendulum_coaster.py b/examples/double_pendulum_coaster.py new file mode 100755 index 0000000..a5586de --- /dev/null +++ b/examples/double_pendulum_coaster.py @@ -0,0 +1,18 @@ +import gcoordinator as gc +import numpy as np + +full_object = [] +p_1 = 20 +P_2 = 20 +rad_1 = 20 +rad_2 = 15 +rad_3 = 10 +for layer in range(50): + theta = np.linspace(0, 2*np.pi, 1000) + x = rad_1 * np.cos(theta) + rad_2 * np.cos(p_1*theta) + rad_3 * np.cos(P_2*theta) + y = rad_1 * np.sin(theta) + rad_2 * np.sin(p_1*theta) + rad_3 * np.sin(P_2*theta) + z = np.full_like(x, layer * 0.2) + wall = gc.Path(x, y, z ) + full_object.append(wall) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/envelope_line.py b/examples/envelope_line.py new file mode 100644 index 0000000..892686f --- /dev/null +++ b/examples/envelope_line.py @@ -0,0 +1,63 @@ +import math +import numpy as np +import gcoordinator as gc + +LAYER=100 +d = 0.8 +gr = 1/1.618 + + +full_object=[] +for height in range(LAYER): + arg = np.linspace(np.pi/4, np.pi*9/4,5) + rad = 40*math.sqrt(2) + x = rad*np.cos(arg) + y = gr*rad*np.sin(arg) + z = np.full_like(arg, height*0.2+0.2) + layer = gc.Path(x, y, z) + full_object.append(layer) + + rad = 40.4*math.sqrt(2) + x = rad*np.cos(arg) + y = gr*rad*np.sin(arg) + z = np.full_like(arg, height*0.2+0.2) + layer = gc.Path(x, y, z) + full_object.append(layer) + + + x = np.linspace(-40,40,10) + y = gr*np.linspace(40,-40,10) + z = np.full_like(x, height * 0.2 + 0.2) + + layer = gc.Path(x, y , z) + full_object.append(layer) + + + if height != 0 and height %3 ==0: + x = np.linspace(40, 40-d*height,10) + y = gr*np.linspace(40-d*height,-40+d*height,10) + z = np.full_like(x,height*0.2+0.2) + layer = gc.Path(x, y, z) + full_object.append(layer) + + x = np.linspace(-40, -40+d*height,10) + y = gr*np.linspace(-40+d*height,+40-d*height,10) + z = np.full_like(x,height*0.2+0.2) + layer = gc.Path(x, y, z) + full_object.append(layer) + + if height != 0 and height %3 ==1: + x = np.linspace(40, 40-d*(height-1),10) + y = gr*np.linspace(40-d*(height-1),-40+d*(height-1),10) + z = np.full_like(x,(height)*0.2+0.2) + layer = gc.Path(x, y, z) + full_object.append(layer) + + x = np.linspace(-40, -40+d*(height-1),10) + y = gr*np.linspace(-40+d*(height-1),+40-d*(height-1),10) + z = np.full_like(x,(height)*0.2+0.2) + layer = gc.Path(x, y, z) + full_object.append(layer) + + +gc.gui_export(full_object) diff --git a/examples/gyroid_container.py b/examples/gyroid_container.py new file mode 100644 index 0000000..c2be600 --- /dev/null +++ b/examples/gyroid_container.py @@ -0,0 +1,33 @@ +import numpy as np +import gcoordinator as gc + +LAYER=80 +nozzle = 0.4 +thickness = 0.2 +a = 4 +L = 40 + + +def quarter_func(arg): + rad = L * (np.cos(arg)**a + np.sin(arg)**a)**(-1/a) + return rad + + +full_object=[] +for height in range(LAYER): + t = np.linspace(0, 2*np.pi, 200) + rad = quarter_func(t) + x = rad * np.cos(t) + y = rad * np.sin(t) + z = np.full_like(x, thickness * (height +1)) + wall = gc.Path(x, y, z) + full_object.append(wall) + outer_wall = gc.Transform.offset(wall, 0.4) + full_object.append(outer_wall) + + if height<20: + gyroid = gc.gyroid_infill(wall, infill_distance = 2) + full_object.append(gyroid) + +gc.gui_export(full_object) + diff --git a/examples/gyroid_infill.py b/examples/gyroid_infill.py new file mode 100644 index 0000000..ef2b33a --- /dev/null +++ b/examples/gyroid_infill.py @@ -0,0 +1,25 @@ +import numpy as np +import gcoordinator as gc + + +LAYER =75 + + + +full_object=[] +for height in range(LAYER): + arg = np.linspace(np.pi/4, np.pi/4+np.pi*2,5) + rad = 70 + x = rad*np.cos(arg)*1.618 + y = rad*np.sin(arg) + z = np.full_like(arg, height*0.2+0.2) + wall = gc.Path(x, y, z) + inner_wall = gc.Transform.offset(wall, -0.7) + infill = gc.gyroid_infill(wall,infill_distance = 8) + wall.z_hop = False + wall.retraction = False + full_object.append(wall) + full_object.append(inner_wall) + full_object.append(infill) + +gc.gui_export(full_object) diff --git a/examples/gyroid_sphere.py b/examples/gyroid_sphere.py new file mode 100644 index 0000000..fe14de6 --- /dev/null +++ b/examples/gyroid_sphere.py @@ -0,0 +1,20 @@ +import numpy as np +import gcoordinator as gc + +LAYER =400 +nozzle = print + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,100) + rad = math.sqrt(50**2 - (height * 0.2-30)**2) + x = rad*np.cos(arg) + y = rad*np.sin(arg) + z = np.full_like(arg, height*0.2+0.2) + wall = gc.Path(x, y, z) + outer_wall = gc.Transform.offset(wall, 0.4) + infill = gc.gyroid_infill(wall, infill_distance = 6) + full_object.append(infill) + +gc.gui_export(full_object) + diff --git a/examples/hyperboloid.py b/examples/hyperboloid.py new file mode 100644 index 0000000..594e9b2 --- /dev/null +++ b/examples/hyperboloid.py @@ -0,0 +1,48 @@ +import math +import numpy as np +import gcoordinator as gc + +LAYER=250 + +def rotation(height): + t = height / LAYER + angle = math.atan(t/(1-t)) + return angle + + +def amp_calc(height): + if height > LAYER * 0.94: + amp = 4 * math.cos((height-0.94 * LAYER)/ (0.06 * LAYER) * np.pi / 2) + elif height < LAYER * 0.06 : + amp = 4 * math.sin((height)/ (0.06 * LAYER) * np.pi / 2) + + else : + amp = 4 + return amp + + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,401) + amp = amp_calc(height) + rad = 40-30*1.5*(height/LAYER)+30*1.5*(height/LAYER)**2+amp*np.floor(abs(1.2*np.sin(arg*25))) + angle = rotation(height) + x = rad*np.cos(arg+angle) + y = rad*np.sin(arg+angle) + z = np.full_like(arg, height*0.3+0.2) + outer_wall = gc.Path(x, y, z) + full_object.append(outer_wall) + + + for i in range(2): + arg = np.linspace(0, np.pi*2,201) + rad = 40-30*1.5*(height/LAYER)+30*1.5*(height/LAYER)**2 - 0.4*(i+1) + angle = rotation(height) + x = rad*np.cos(arg+angle) + y = rad*np.sin(arg+angle) + z = np.full_like(arg, height*0.3+0.2) + inner_wall = gc.Path(x, y, z) + full_object.append(inner_wall) + +gc.gui_export(full_object) + diff --git a/examples/kinematics/S_shaped_tube.py b/examples/kinematics/S_shaped_tube.py new file mode 100644 index 0000000..5c2d98a --- /dev/null +++ b/examples/kinematics/S_shaped_tube.py @@ -0,0 +1,35 @@ +import numpy as np +import gcoordinator as gc + +LAYER =100 +nozzle = 0.4 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, 2 * np.pi , 100) + rad = 10 + x = rad * np.cos(arg) + y = rad * np.sin(arg) + z = np.full_like(x, 0) + p = np.column_stack([x, y, z]) + phi = height/LAYER * np.pi/2 + R = 50 + + X_0 = [R* np.cos(phi)-R, 0, R* np.sin(phi)] + + PHI_inv = np.array([[np.cos(phi), 0, -np.sin(phi)], + [0, 1, 0], + [np.sin(phi), 0, np.cos(phi)]]) + X = np.dot( ( p + X_0), PHI_inv) + + x = X[:, 0] + y = X[:, 1] + z = X[:, 2] + tilt = np.full_like(x, np.pi/2*height/LAYER) + rot = np.full_like( tilt, 0) + circle = gc.Path(x, y, z, rot , tilt) + full_object.append(circle) + + +gc.gui_export(full_object) + diff --git a/examples/kinematics/default_cylinder_bed_rotate.py b/examples/kinematics/default_cylinder_bed_rotate.py new file mode 100644 index 0000000..be272a6 --- /dev/null +++ b/examples/kinematics/default_cylinder_bed_rotate.py @@ -0,0 +1,24 @@ + + +# Please execute this code after changing the machine setting's +# kinematics to 'bed_rotate_bc'. +# Author: @_gear_geek_' + + +import numpy as np +import gcoordinator as gc +import math +LAYER =50 + +full_object=[] +for height in range(LAYER): + rad = 10 + x = [rad, rad] + y = [0, 0] + buf_z = height*0.2+0.2 + z = [buf_z, buf_z] + rot = [math.pi*2*height, math.pi*2*(height+1)] + wall = gc.Path(x, y, z, rot) + full_object.append(wall) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/kinematics/default_cylinder_bed_tilt_bc.py b/examples/kinematics/default_cylinder_bed_tilt_bc.py new file mode 100644 index 0000000..095ee99 --- /dev/null +++ b/examples/kinematics/default_cylinder_bed_tilt_bc.py @@ -0,0 +1,26 @@ + + +# Please execute this code after changing the machine setting's +# kinematics to 'bed_tilt_bc'. +# Author: @_gear_geek_' + + +import numpy as np +import math +import gcoordinator as gc + +LAYER =50 + +full_object=[] +for height in range(LAYER): + rad = 10 + x = [rad, rad] + y = [0, 0] + buf_z = height*0.2+0.2 + z = [buf_z, buf_z] + rot = [math.pi*2*height, math.pi*2*(height+1)] + tilt = [math.pi/4/LAYER*height, math.pi/4/LAYER*(height+1)] + wall = gc.Path(x, y, z, rot, tilt) + full_object.append(wall) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/kinematics/default_cylinder_nozzle_tilt.py b/examples/kinematics/default_cylinder_nozzle_tilt.py new file mode 100644 index 0000000..bddc50c --- /dev/null +++ b/examples/kinematics/default_cylinder_nozzle_tilt.py @@ -0,0 +1,26 @@ + + +# Please execute this code after changing the machine setting's +# kinematics to 'nozzle_tilt'. +# Author: @_gear_geek_' + + +import numpy as np +import math +import gcoordinator as gc +LAYER =50 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,100) + rad = 10 + x = rad*np.cos(arg) + y = rad*np.sin(arg) + z = np.full_like(arg, height*0.2+0.2) + rot = arg + tilt = [np.pi/2/100*height]*len(x) + wall = gc.Path(x, y, z, rot, tilt) + full_object.append(wall) + +gc.gui_export(full_object) + diff --git a/examples/kinematics/nozzle_tilt_demo_object.py b/examples/kinematics/nozzle_tilt_demo_object.py new file mode 100644 index 0000000..238436e --- /dev/null +++ b/examples/kinematics/nozzle_tilt_demo_object.py @@ -0,0 +1,26 @@ +import numpy as np +import math +import gcoordinator as gc + +LAYER =50 + +full_object=[] +radius = 20 +cnt = 0 +while radius < 75: + angle = np.linspace(0, np.pi,180) + if cnt%2==0: + x = radius * np.cos(angle) + else: + x = -radius * np.cos(angle) + y = [0] * len(x) + z = radius * np.sin(angle) + rot = [math.pi / 2.0] * len(x) + tilt = [math.pi / 4.0] * len(x) + wall = gc.Path(x, y, z, rot, tilt) + wall.extrusion_multiplier = 10.0 + full_object.append(wall) + radius += 0.6 + cnt += 1 + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/koch_curve.py b/examples/koch_curve.py new file mode 100644 index 0000000..701b14f --- /dev/null +++ b/examples/koch_curve.py @@ -0,0 +1,60 @@ +import numpy as np +import gcoordinator as gc + +x_coords = np.array([0]) +y_coords = np.array([0]) +def draw_koch_snowflake(order, length, angle, x, y): + global x_coords, y_coords + if order == 0: + x_end = x + length * np.cos(np.radians(angle)) + y_end = y + length * np.sin(np.radians(angle)) + x_coords = np.append(x_coords, x_end) + y_coords = np.append(y_coords, y_end) + else: + length /= 3.0 + draw_koch_snowflake(order - 1, length, angle, x, y) + x = x + length * np.cos(np.radians(angle)) + y = y + length * np.sin(np.radians(angle)) + draw_koch_snowflake(order - 1, length, angle - 60, x, y) + x = x + length * np.cos(np.radians(angle - 60)) + y = y + length * np.sin(np.radians(angle - 60)) + draw_koch_snowflake(order - 1, length, angle + 60, x, y) + x = x + length * np.cos(np.radians(angle + 60)) + y = y + length * np.sin(np.radians(angle + 60)) + draw_koch_snowflake(order - 1, length, angle, x, y) + + + +R = 150 +arg = np.pi/5 +LAYER = int(R / 0.2 * np.cos(arg)) + + +full_object=[] +for height in range(LAYER): + x_coords = np.array([0]) + y_coords = np.array([0]) + + a = np.cos(arg)*2 * height /LAYER * R - np.cos(arg)*R + + initial_length =np.sqrt(R**2 - a**2) + initial_angle = 0 + draw_koch_snowflake(4, initial_length, initial_angle, 0, 0) + draw_koch_snowflake(4, initial_length, initial_angle + 120, initial_length, 0) + draw_koch_snowflake(4, initial_length, initial_angle +240, initial_length/2, initial_length/2*np.sqrt(3)) + + z = np.full_like(x_coords, height*0.2 + 0.2) + wall = gc.Path(x_coords, y_coords, z) + wall = gc.Transform.move(wall, -initial_length/2, -initial_length/2*np.sqrt(3)*1/3) + wall = gc.Transform.rotate_xy(wall, -height/LAYER * np.pi/2) + full_object.append(wall) + + if height<3: + infill = gc.line_infill(wall, infill_distance = 1, angle=height*np.pi/2 + np.pi/4) + infill.z_hop = True + infill.retraction = True + full_object.append(infill) + +gc.gui_export(full_object) + + diff --git a/examples/lamp_shade.py b/examples/lamp_shade.py new file mode 100644 index 0000000..4208e90 --- /dev/null +++ b/examples/lamp_shade.py @@ -0,0 +1,38 @@ +import numpy as np +import gcoordinator as gc + +LAYER = 400 + +def rotation_calc(height): + rad = np.sqrt(50**2 - (height * 0.2-35)**2) + return height*0.004 + +def print_polar(arg, rad, height): + rotation = rotation_calc(height) + x = rad*np.cos(arg + rotation) + y = rad*np.sin(arg + rotation) + z = np.full_like(arg, height*0.2+0.2) + layer = gc.Path(x,y,z) + return layer + + +full_object=[] +R = 50 +layer_thick = 0.2 +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,801) + rad = np.sqrt(R**2 - (height * layer_thick-35)**2) + rad += 3 * np.floor(1.05 * abs( np.sin(arg * 50))) + wall = print_polar(arg, rad, height) + full_object.append(wall) + +for height in range(400, 426): + arg = np.linspace(0, np.pi*2,401) + rad = np.sqrt(R**2 - (399 * layer_thick-35)**2) + top_wall = print_polar(arg, rad, height) + full_object.append(top_wall) + full_object.append(gc.Transform.offset(top_wall, -0.4)) + + + +gc.gui_export(full_object) diff --git a/examples/lidded_cylinder.py b/examples/lidded_cylinder.py new file mode 100644 index 0000000..68b7ac4 --- /dev/null +++ b/examples/lidded_cylinder.py @@ -0,0 +1,20 @@ +import numpy as np +import gcoordinator as gc + +LAYER=50 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,100) + rad = 30 + x = rad*np.cos(arg) + y = rad*np.sin(arg) + z = np.full_like(arg, height*0.4+0.4) + layer = gc.Path(x,y,z) + full_object.append(layer) + offset = gc.Transform.offset(layer, 0) +for i in range(70): + offset = gc.Transform.offset(offset, - 0.4) + full_object.append(offset) + +gc.gui_export(full_object) diff --git a/examples/plate.py b/examples/plate.py new file mode 100644 index 0000000..41a90a4 --- /dev/null +++ b/examples/plate.py @@ -0,0 +1,18 @@ +import numpy as np +import gcoordinator as gc + +nozzle = 0.4 +thickness = 0.2 +LAYER = 2 + +full_object=[] +for height in range(LAYER): + x = np.array([100,-100,-100,100,100], dtype = float) + y = np.array([100,100,-100,-100,100], dtype = float) + z = np.full_like(x, (height+1)*thickness) + wall = gc.Path(x, y, z) + infill = gc.line_infill(wall, infill_distance=0.4) + full_object.append(wall) + full_object.append(infill) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/polygon_case.py b/examples/polygon_case.py new file mode 100644 index 0000000..f5feeef --- /dev/null +++ b/examples/polygon_case.py @@ -0,0 +1,21 @@ +import numpy as np +import gcoordinator as gc + +LAYER=50 +N = 6 #polygon number + + +full_object = [] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,N+1) + rad = 25 + x = rad*np.cos(arg) + y = rad*np.sin(arg) + z = np.full_like(arg, height*0.2+0.2) + wall = gc.Path(x, y, z) + full_object.append(wall) + if height==0: + bottom = gc.line_infill(wall, infill_distance = 0.4) + full_object.append(bottom) + +gc.gui_export(full_object) diff --git a/examples/random_maze.py b/examples/random_maze.py new file mode 100644 index 0000000..1da0ff9 --- /dev/null +++ b/examples/random_maze.py @@ -0,0 +1,33 @@ +import random +import numpy as np +import gcoordinator as gc + +nozzle = 0.4 +thickness = 0.2 + +LAYER = 20 +s = 10 +def line(x_pos, y_pos, a): + if a: + x = np.array([x_pos*s, x_pos*s+s], dtype = float) + y = np.array([y_pos*s, y_pos*s+s], dtype = float) + return x-100, y-100 + else: + x = np.array([x_pos*s, x_pos*s+s], dtype = float) + y = np.array([y_pos*s+s, y_pos*s], dtype = float) + return x-100, y-100 + + +full_object=[] +random_slope = [[ bool(random.getrandbits(1)) for j in range(20)] for j in range(20)] +for height in range(LAYER): + for i in range(20): + for j in range(20): + a = random_slope[i][j] + x, y = line(i, j, a) + z = np.full_like(x, (height+1)*thickness+1) + seg = gc.Path(x, y, z) + full_object.append(seg) + + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/rectangle_wall.py b/examples/rectangle_wall.py new file mode 100644 index 0000000..e174cf5 --- /dev/null +++ b/examples/rectangle_wall.py @@ -0,0 +1,15 @@ +import numpy as np +import gcoordinator as gc + +LAYER=50 + +full_object=[] +for height in range(LAYER): + x = np.array([10,-10,-10,10, 10], dtype = float) + y = np.array([10,10,-10,-10, 10], dtype = float) + z = np.full_like(x, height*0.2+0.2) + layer = gc.Path(x, y, z) + + full_object.append(layer) + +gc.gui_export(full_object) diff --git a/examples/ripples_wall.py b/examples/ripples_wall.py new file mode 100644 index 0000000..385c165 --- /dev/null +++ b/examples/ripples_wall.py @@ -0,0 +1,32 @@ +import math +import numpy as np +import gcoordinator as gc + +LAYER=500 + +def function(height ,i): + value = 4 * math.cos(math.sqrt((i*24-12)**2+(height/LAYER*24-12)**2))*math.exp(-((i*24-12)/10)**2-((height/LAYER*24-12)/10)**2) + return value + + +full_object=[] +for height in range(LAYER): + t = np.linspace(0,1,200) + x = t*100-50 + y = [-6*math.floor( abs(1.05 * math.sin(i * 2 * np.pi * 20))) + function(height,i) for i in t] + z = np.full_like(t, height*0.2+0.2) + layer = gc.Path(x, y, z) + full_object.append(layer) + + + t = np.linspace(0,1,200) + x = t*100-50 + y=[0.4+function(height,i) for i in t] + z = np.full_like(t, height*0.2+0.2) + wall = gc.Path(x, y, z) + full_object.append(wall) + wall_2 = gc.Transform.offset(wall, -0.4) + full_object.append(wall_2) + +gc.gui_export(full_object) + diff --git a/examples/spiral_staircase.py b/examples/spiral_staircase.py new file mode 100755 index 0000000..30f649f --- /dev/null +++ b/examples/spiral_staircase.py @@ -0,0 +1,60 @@ +import numpy as np +from tqdm import tqdm +import gcoordinator as gc + +full_object = [] +LAYER = 1000 +band = np.pi/8 +twist = 1.5 * np.pi +for height in tqdm(range(LAYER)): + t = height/LAYER + arg = np.linspace(0, 2*np.pi, 100) + x = 20 * np.cos(arg) + y = 20 * np.sin(arg) + z = np.full_like(x, height * 0.2) + center_pole = gc.Path(x, y, z) + center_pole_2 = gc.Transform.offset(center_pole, -0.4) + center_pole_2.z_hop = False + center_pole_2.retraction = False + full_object.append(center_pole_2) + full_object.append(center_pole) + if height<3: + infill = gc.line_infill(center_pole, infill_distance = 2, angle =np.pi/4+np.pi/2*height) + infill.retraction = False + infill.z_hop = False + #full_object.append(infill) + + arg = np.linspace(t*twist, t*twist+band, 20) + arg_inv = np.linspace(t*twist+band, t*twist, 20) + x = 60 * np.cos(arg) + y = 60 * np.sin(arg) + x_inv = 60.4 * np.cos(arg_inv) + y_inv = 60.4 * np.sin(arg_inv) + z = np.full_like(x, height * 0.2) + wall = gc.Path(x, y, z) + wall.z_hop = False + wall.retraction = False + outer_wall = gc.Path(x_inv, y_inv, z) + wall_2 = gc.Transform.rotate_xy(wall, np.pi) + wall_2.z_hop = False + wall_2.retraction = False + outer_wall_2 = gc.Transform.rotate_xy(outer_wall, np.pi) + + rot = int(t*twist*10)/10 + x = [60.4*np.cos(rot+band/2), 19.6*np.cos(rot+band/2)] + y = [60.4*np.sin(rot+band/2), 19.6*np.sin(rot+band/2)] + z = np.full_like(x, height * 0.2) + bridge = gc.Path(x, y, z) + bridge.print_speed = 3000 + bridge_2 = gc.Transform.rotate_xy(bridge, np.pi) + bridge_2.print_speed = 3000 + + full_object.append(wall) + full_object.append(outer_wall) + full_object.append(bridge) + + full_object.append(wall_2) + full_object.append(outer_wall_2) + full_object.append(bridge_2) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/stackable_case_wavy.py b/examples/stackable_case_wavy.py new file mode 100755 index 0000000..d0f4032 --- /dev/null +++ b/examples/stackable_case_wavy.py @@ -0,0 +1,76 @@ +# ========================================================== +# This code example is provided by @Mithril_MEX on X (twitter). +# https://twitter.com/Mithril_MEX +# +# Description: +# This version, stackable_case, includes additional modifications: +# - Added skirt functionality +# - Adjusted initial phase (deltat) +# - Improved the quarter_func of the round function (now supports 'a' not being a power of 2) +# - Added undulations to the wall surface +# ========================================================== + +import numpy as np +import gcoordinator as gc + +LAYER=120 +resolution = 200 +nozzle = 0.4 +thickness = 0.2 + +a = 4 +L = 50 +depth = 35 +deltat = 2*np.pi*0.25 +amp = 0.5 +omega = 4*8 + +def quarter_func(arg): + rad = L * (abs(np.cos(arg))**a + abs(np.sin(arg))**a)**(-1/a) + return rad + +full_object=[] + +for height in range(LAYER): + t = np.linspace(0+deltat, 2*np.pi+deltat, resolution) + rad = quarter_func(t) + base_x = rad * np.cos(t) + base_y = rad * np.sin(t) + thetaL = t*omega + thetaH = height*omega*np.pi/LAYER + ripple_x = amp*np.cos(thetaL)*np.sin(thetaH/4) + ripple_y = amp*np.sin(thetaL)*np.sin(thetaH/4) + x = base_x + ripple_x + y = base_y + ripple_y + z = np.full_like(x, thickness * (height +1)) + wall = gc.Path(x, y, z) + wall.print_speed = 3000 + wall.extrusion_multiplier = 1.0 + + if height == 0: + inner_wall = gc.Transform.offset(wall, nozzle*8) + inner_wall.print_speed = 1000 + inner_wall.extrusion_multiplier = 5.0 + full_object.append(inner_wall) + + if height>depth: + wall = gc.Transform.offset(wall, nozzle*4) + full_object.append(wall) + + for i in range(2): + inner_wall = gc.Transform.offset(wall, -nozzle*(i+1)) + full_object.append(inner_wall) + + if height == depth or height == depth-1: + offset = wall + for i in range(4): + offset = gc.Transform.offset(offset, nozzle) + full_object.append(offset) + + if height <2: + infill_wall = gc.Transform.offset(inner_wall, -nozzle) + bottom = gc.line_infill(infill_wall,infill_distance = 2*nozzle, angle = np.pi/4+np.pi/2*height) + full_object.append(infill_wall) + full_object.append(bottom) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/stakable_case.py b/examples/stakable_case.py new file mode 100644 index 0000000..d336441 --- /dev/null +++ b/examples/stakable_case.py @@ -0,0 +1,51 @@ +import numpy as np +import gcoordinator as gc + +LAYER=120 +nozzle = 0.4 +thickness = 0.2 +a = 4 +L = 40 +depth = 35 + + +def quarter_func(arg): + rad = L * (np.cos(arg)**a + np.sin(arg)**a)**(-1/a) + return rad + +full_object=[] +for height in range(LAYER): + t = np.linspace(0, 2*np.pi, 200) + rad = quarter_func(t) + x = rad * np.cos(t) + y = rad * np.sin(t) + z = np.full_like(x, thickness * (height +1)) + wall = gc.Path(x, y, z) + + if height>depth: + wall = gc.Transform.offset(wall, nozzle*4) + full_object.append(wall) + + for i in range(2): + inner_wall = gc.Transform.offset(wall, -nozzle*(i+1)) + full_object.append(inner_wall) + + if height == depth or height == depth-1: + offset = wall + for i in range(4): + offset = gc.Transform.offset(offset, nozzle) + full_object.append(offset) + + if height <2: + infill_wall = gc.Transform.offset(inner_wall, -nozzle) + infill_wall.z_hop = True + infill_wall.retraction = True + bottom = gc.line_infill(infill_wall, infill_distance = 2*nozzle, angle = np.pi/4 + np.pi/2*height) + bottom.z_hop = True + bottom.retraction = True + full_object.append(infill_wall) + full_object.append(bottom) + + +gc.gui_export(full_object) + diff --git a/examples/twin_cylinder.py b/examples/twin_cylinder.py new file mode 100755 index 0000000..8b954b9 --- /dev/null +++ b/examples/twin_cylinder.py @@ -0,0 +1,47 @@ +import numpy as np +import gcoordinator as gc + +full_object = [] +LAYER = 400 +rad = 30 + +rot = np.pi/2 +for height in range(LAYER): + arg = np.linspace(0, np.pi, 100) + x = rad * np.cos(arg) + y = rad * np.sin(arg) + x = np.append(x, x[0]) + y = np.append(y, y[0]) + z = np.full_like(x, height * 0.2) + wall_1 = gc.Path(x, y, z) + wall_1 = gc.Transform.rotate_xy(wall_1, height/LAYER *rot) + wall_1_inner = gc.Transform.offset(wall_1, -0.4) + wall_1_inner.retraction = True + wall_1_inner.z_hop = True + + full_object.append(wall_1) + full_object.append(wall_1_inner) + if height<6: + base = gc.line_infill(wall_1, infill_distance=0.8, angle=np.pi/4+np.pi/2*height) + full_object.append(base) + + arg = np.linspace(np.pi, 2*np.pi, 100) + x = rad * np.cos(arg) + y = rad * np.sin(arg) + x = np.append(x, x[0]) + y = np.append(y, y[0]) + z = np.full_like(x, height * 0.2) + wall_2 = gc.Path(x, y, z) + wall_2 = gc.Transform.rotate_xy(wall_2, height/LAYER *rot) + wall_2 = gc.Transform.move(wall_2, -5,-5, 0) + wall_2_inner = gc.Transform.offset(wall_2, -0.4) + wall_2_inner.retraction = True + wall_2_inner.z_hop = True + + full_object.append(wall_2) + full_object.append(wall_2_inner) + if height<6: + base = gc.line_infill(wall_2, infill_distance=0.8, angle=np.pi/4+np.pi/2*height) + full_object.append(base) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/twist_cup.py b/examples/twist_cup.py new file mode 100644 index 0000000..4e2b646 --- /dev/null +++ b/examples/twist_cup.py @@ -0,0 +1,39 @@ +import numpy as np +import gcoordinator as gc + +LAYER=380 +def rotation_calc(height): + x=height/LAYER + rotation=math.sin((x)*2.5*np.pi) + return rotation*0.3 + +full_object=[] +for height in range(LAYER): + arg=np.linspace(0,2*np.pi,481) + rad=22+ 4*np.floor(1.05*abs(np.sin(arg*40))) + if height/LAYER<=0.25: + rad=rad-5*(1-np.sin((height/LAYER)/0.25*np.pi/2)) + rotation = rotation_calc(height) + + x = rad*np.cos(arg+rotation) + y = rad*np.sin(arg+rotation) + z = np.full_like(arg, height*0.2+0.2) + wall = gc.Path(x, y, z) + full_object.append(wall) + + + + # _c means inner wall + arg_c=np.linspace(0,-2*np.pi,201) + rad_c=22-0.25 + if height/LAYER<=0.25: + rad_c = rad_c-5*(1-np.sin((height/LAYER)/0.25*np.pi/2)) + rotation = rotation_calc(height) + x_c = rad_c*np.cos(arg_c+rotation) + y_c = rad_c*np.sin(arg_c+rotation) + z_c = np.full_like(arg_c, height*0.2+0.2) + wall_c = gc.Path(x_c, y_c, z_c) + full_object.append(wall_c) + + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/wave_bottle.py b/examples/wave_bottle.py new file mode 100644 index 0000000..7c78644 --- /dev/null +++ b/examples/wave_bottle.py @@ -0,0 +1,49 @@ +import numpy as np +import gcoordinator as gc + + +LAYER=110 +base_rad = 35 + + +def wave(x): + y = np.arccos(np.cos(x + np.pi/2))/np.pi * 2 -1 + return y + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,203) + amp = 3 + z = np.linspace(height*1,(height+1)*1,203) + rad = base_rad + off_rad = 8 + + if height < LAYER* 0.175: + rad += 0 + rad += off_rad*np.sin((z/LAYER) * 5*np.pi/2) + else: + rad += amp*np.sin(arg*50.5+height*np.pi)+off_rad + b = 6 * np.sin((z / LAYER-0.175) * np.pi * 3.5 ) + a = 3 + rad += b * np.sin(a * arg) + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + wave_wall = gc.Path(x, y, z) + full_object.append(wave_wall) + + + + if height <2: + arg = np.linspace(0, np.pi*2,401) + rad = base_rad-0.7 + z = np.full_like(arg, height*1+1) + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + + inner_wall = gc.Path(x, y, z) + full_object.append(inner_wall) + bottom = gc.line_infill(inner_wall, infill_distance = 1, angle = np.pi/4+ np.pi/2 * height) + full_object.append(bottom) + + +gc.gui_export(full_object) diff --git a/examples/wave_bottle_2.py b/examples/wave_bottle_2.py new file mode 100644 index 0000000..363e105 --- /dev/null +++ b/examples/wave_bottle_2.py @@ -0,0 +1,33 @@ +import numpy as np +import gcoordinator as gc + +LAYER=90 +base_rad = 35 + + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,143) + amp = 3 + z = np.linspace(height*1,(height+1)*1,143) + rad = base_rad+amp*np.sin(arg*35.5+height*np.pi) + x = rad*np.cos(arg )+7*np.sin(z/LAYER*2*np.pi*3.2) + y = rad*np.sin(arg ) + wave_wall = gc.Path(x, y, z) + full_object.append(wave_wall) + + if height <2: + arg = np.linspace(0, np.pi*2,401) + rad = base_rad-3 + z = np.full_like(arg, height*1+1) + x = rad*np.cos(arg )+7*np.sin(z/LAYER*2*np.pi*3.2) + y = rad*np.sin(arg ) + inner_wall = gc.Path(x, y, z) + full_object.append(inner_wall) + + bottom = gc.line_infill(inner_wall,infill_distance = 1, angle = np.pi/4 + np.pi/2 * height) + full_object.append(bottom) + + +gc.gui_export(full_object) + diff --git a/examples/wave_bottle_3.py b/examples/wave_bottle_3.py new file mode 100644 index 0000000..83b344f --- /dev/null +++ b/examples/wave_bottle_3.py @@ -0,0 +1,30 @@ +import numpy as np +import gcoordinator as gc + +LAYER=150 +base_rad = 50 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,203) + amp = 2 + rad = base_rad+amp*np.sin(arg*50.5+np.pi*height) + 5*np.sin((height+arg/(2*np.pi))/LAYER*np.pi*5) + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + z = np.linspace(height*0.7,(height+1)*0.7,203) + wave_wall = gc.Path(x, y, z) + full_object.append(wave_wall) + + if height <2: + arg = np.linspace(0, np.pi*2,401) + rad = base_rad-2 + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + z = np.full_like(arg, height*0.7+0.7) + inner_wall = gc.Path(x, y, z) + full_object.append(inner_wall) + bottom = gc.line_infill(inner_wall, infill_distance = 1.2, angle = np.pi/4 + np.pi/2*height) + full_object.append(bottom) + + +gc.gui_export(full_object) diff --git a/examples/wave_cylinder.py b/examples/wave_cylinder.py new file mode 100644 index 0000000..462c593 --- /dev/null +++ b/examples/wave_cylinder.py @@ -0,0 +1,31 @@ +import numpy as np +import gcoordinator as gc + +LAYER=100 +base_rad = 50 + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,203) + amp = 2 + rad = base_rad+amp*np.sin(arg*50.5+np.pi*height) + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + z = np.linspace(height*0.7,(height+1)*0.7,203) + wave_wall = gc.Path(x, y, z) + full_object.append(wave_wall) + + if height <2: + arg = np.linspace(0, np.pi*2,401) + rad = base_rad-2 + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + z = np.full_like(arg, height*0.7+0.7) + inner_wall = gc.Path(x, y, z) + full_object.append(inner_wall) + bottom = gc.line_infill(inner_wall, infill_distance = 1, angle = np.pi/4 + np.pi/2 *height) + full_object.append(bottom) + + +gc.gui_export(full_object) + diff --git a/examples/wave_tissue_case.py b/examples/wave_tissue_case.py new file mode 100644 index 0000000..db8f8f1 --- /dev/null +++ b/examples/wave_tissue_case.py @@ -0,0 +1,62 @@ +# Author: Yadanium (@yada_kaeru) + +import numpy as np +import gcoordinator as gc + +LAYER = 400 +layer_hight = 0.5 + +#1.0mmノズルで計算した高さ +#1.0mmノズルの場合layer_hightは0.5 +#原点(origin)x=50, y=50 +#速度(speed)print_speed及びtravel_speedは1000 +#リトラクション有り + +#点と点の間をsin波で補間する +def create_sinusoidal_path(x_points, y_points, num_points=300, amplitude=2, phase_shift=0): +#amplitudeの値で厚みを変えられます + + result_x = [] + result_y = [] + + for i in range(len(x_points)-1): + x1, x2 = x_points[i], x_points[i+1] + y1, y2 = y_points[i], y_points[i+1] + + # 2点間の距離を計算 + distance = np.sqrt((x2-x1)**2 + (y2-y1)**2) + # 距離に応じて波の数を調整(長い線分はより多くの波を持つ) + num_waves = max(0, int(distance/6)) + + t = np.linspace(0, 1, num_points) + + # 直線上の点を計算 + line_x = x1 + (x2 - x1) * t + line_y = y1 + (y2 - y1) * t + + # 直線の角度を計算 + angle = np.arctan2(y2 - y1, x2 - x1) + + # より細かいsin波を適用 + dx = amplitude * np.sin(2 * np.pi * num_waves * t + phase_shift) * np.sin(angle) + dy = -amplitude * np.sin(2 * np.pi * num_waves * t + phase_shift) * np.cos(angle) + + result_x.extend(line_x + dx) + result_y.extend(line_y + dy) + + return np.array(result_x), np.array(result_y) + +full_object = [] +for height in range(LAYER): + x_orig = (50,50,48,48,0,0,106,106,58,58,56,56) + y_orig = (62,67.5,67.5,64,64,0,0,64,64,67.5,67.5,62) + + # 偶数層と奇数層で位相をずらす + phase = np.pi if height % 2 == 1 else 0 + x, y = create_sinusoidal_path(x_orig, y_orig, phase_shift=phase) + z = np.full_like(x, (height+1)*layer_hight+1) + + wall = gc.Path(x, y, z) + full_object.append(wall) + +gc.gui_export(full_object) \ No newline at end of file diff --git a/examples/wave_tray.py b/examples/wave_tray.py new file mode 100644 index 0000000..cf83d38 --- /dev/null +++ b/examples/wave_tray.py @@ -0,0 +1,34 @@ +import numpy as np +import gcoordinator as gc + +LAYER=60 +base_rad = 80 + + +full_object=[] +for height in range(LAYER): + arg = np.linspace(0, np.pi*2,403) + amp = 2 + z = np.linspace(height*1,(height+1)*1,403) + rad = base_rad + rad += 7*np.sin((z/LAYER)*np.pi) + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + circle = gc.Path(x, y, z) + + + rad+=amp*np.sin(arg*100.5+np.pi*height) + x = rad*np.cos(arg ) + y = rad*np.sin(arg ) + wave_wall = gc.Path(x, y, z) + full_object.append(wave_wall) + + if height<2: + inner_wall = gc.Transform.offset(circle, -2) + full_object.append(inner_wall) + bottom = gc.line_infill(inner_wall, infill_distance = 2, angle = np.pi/4 + np.pi/2*height) + full_object.append(bottom) + + + +gc.gui_export(full_object) diff --git a/img/G-coordinator.ico b/img/G-coordinator.ico new file mode 100644 index 0000000..0b2c123 Binary files /dev/null and b/img/G-coordinator.ico differ diff --git a/img/G-coordinator.png b/img/G-coordinator.png new file mode 100644 index 0000000..a321d84 Binary files /dev/null and b/img/G-coordinator.png differ diff --git a/img/default_cylinder.png b/img/default_cylinder.png new file mode 100644 index 0000000..a239c9a Binary files /dev/null and b/img/default_cylinder.png differ diff --git a/img/gcode_export_window.png b/img/gcode_export_window.png new file mode 100644 index 0000000..861221e Binary files /dev/null and b/img/gcode_export_window.png differ diff --git a/img/logo.svg b/img/logo.svg new file mode 100644 index 0000000..9dbc207 --- /dev/null +++ b/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/machine_settings.png b/img/machine_settings.png new file mode 100644 index 0000000..a684b98 Binary files /dev/null and b/img/machine_settings.png differ diff --git a/img/modeling.gif b/img/modeling.gif index 1827c25..88c4236 100644 Binary files a/img/modeling.gif and b/img/modeling.gif differ diff --git a/img/nozzle_path.png b/img/nozzle_path.png new file mode 100644 index 0000000..3dcfa22 Binary files /dev/null and b/img/nozzle_path.png differ diff --git a/img/print_settings.png b/img/print_settings.png new file mode 100644 index 0000000..5caa5b1 Binary files /dev/null and b/img/print_settings.png differ diff --git a/img/start.png b/img/start.png new file mode 100644 index 0000000..beb1ec8 Binary files /dev/null and b/img/start.png differ diff --git a/img/test_modeling.png b/img/test_modeling.png index 1ac73e5..a239c9a 100644 Binary files a/img/test_modeling.png and b/img/test_modeling.png differ diff --git a/img/wave_tray.JPG b/img/wave_tray.JPG new file mode 100755 index 0000000..a1f4284 Binary files /dev/null and b/img/wave_tray.JPG differ diff --git a/img/works/audrey.jpg b/img/works/audrey.jpg new file mode 100644 index 0000000..8128e06 Binary files /dev/null and b/img/works/audrey.jpg differ diff --git a/img/works/audrey_2.jpg b/img/works/audrey_2.jpg new file mode 100755 index 0000000..32e7265 Binary files /dev/null and b/img/works/audrey_2.jpg differ diff --git a/img/works/clock.jpg b/img/works/clock.jpg new file mode 100644 index 0000000..f301842 Binary files /dev/null and b/img/works/clock.jpg differ diff --git a/img/works/envelope_1.jpg b/img/works/envelope_1.jpg new file mode 100644 index 0000000..6becf4c Binary files /dev/null and b/img/works/envelope_1.jpg differ diff --git a/img/works/envelope_2.jpg b/img/works/envelope_2.jpg new file mode 100644 index 0000000..50212d5 Binary files /dev/null and b/img/works/envelope_2.jpg differ diff --git a/img/works/gyroid_coaster.jpg b/img/works/gyroid_coaster.jpg new file mode 100644 index 0000000..0ff796e Binary files /dev/null and b/img/works/gyroid_coaster.jpg differ diff --git a/img/works/gyroid_wall.jpg b/img/works/gyroid_wall.jpg new file mode 100644 index 0000000..751cd64 Binary files /dev/null and b/img/works/gyroid_wall.jpg differ diff --git a/img/works/light_cup.jpg b/img/works/light_cup.jpg new file mode 100644 index 0000000..21cb248 Binary files /dev/null and b/img/works/light_cup.jpg differ diff --git a/img/works/light_fixture.jpg b/img/works/light_fixture.jpg new file mode 100755 index 0000000..26090ed Binary files /dev/null and b/img/works/light_fixture.jpg differ diff --git a/img/works/lissajous_1.jpg b/img/works/lissajous_1.jpg new file mode 100644 index 0000000..1b01237 Binary files /dev/null and b/img/works/lissajous_1.jpg differ diff --git a/img/works/lissajous_2.jpg b/img/works/lissajous_2.jpg new file mode 100644 index 0000000..3bbc109 Binary files /dev/null and b/img/works/lissajous_2.jpg differ diff --git a/img/works/others.jpg b/img/works/others.jpg new file mode 100644 index 0000000..6fef668 Binary files /dev/null and b/img/works/others.jpg differ diff --git a/img/works/sin_cup_1.jpg b/img/works/sin_cup_1.jpg new file mode 100644 index 0000000..ad36338 Binary files /dev/null and b/img/works/sin_cup_1.jpg differ diff --git a/img/works/sin_wall.jpg b/img/works/sin_wall.jpg new file mode 100755 index 0000000..be1b3e1 Binary files /dev/null and b/img/works/sin_wall.jpg differ diff --git a/img/works/wave_bottle_2.jpg b/img/works/wave_bottle_2.jpg new file mode 100644 index 0000000..4d91049 Binary files /dev/null and b/img/works/wave_bottle_2.jpg differ diff --git a/img/works/wave_cup.jpg b/img/works/wave_cup.jpg new file mode 100644 index 0000000..141a101 Binary files /dev/null and b/img/works/wave_cup.jpg differ diff --git a/img/works/wave_cup_2.jpg b/img/works/wave_cup_2.jpg new file mode 100644 index 0000000..b0f2fd9 Binary files /dev/null and b/img/works/wave_cup_2.jpg differ diff --git a/img/works/wave_tray_1.jpg b/img/works/wave_tray_1.jpg new file mode 100644 index 0000000..ecd9a7f Binary files /dev/null and b/img/works/wave_tray_1.jpg differ diff --git a/img/works/wave_wall.gif b/img/works/wave_wall.gif new file mode 100644 index 0000000..6ce4338 Binary files /dev/null and b/img/works/wave_wall.gif differ diff --git a/img/works/wave_wall_1.jpg b/img/works/wave_wall_1.jpg new file mode 100644 index 0000000..1240e8d Binary files /dev/null and b/img/works/wave_wall_1.jpg differ diff --git a/requirements.txt b/requirements.txt index ea65152..d3e4c9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,21 @@ -numpy==1.24.1 -PyOpenGL==3.1.6 -PyQt5==5.15.7 -PyQt5-Qt5==5.15.2 -PyQt5-sip==12.11.0 -pyqtgraph==0.13.1 +contourpy==1.2.0 +cycler==0.12.1 +darkdetect==0.7.1 +fonttools==4.44.0 +gcoordinator==0.0.13 +importlib-resources==6.1.1 +kiwisolver==1.4.5 +matplotlib==3.7.1 +numpy==1.26.2 +packaging==23.2 +pillow==11.1.0 +PyOpenGL==3.1.7 +pyparsing==3.1.1 +PyQt5==5.15.10 +PyQt5-Qt5==5.15.16 +PyQt5-sip==12.13.0 +pyqtdarktheme==2.1.0 +pyqtgraph==0.13.2 +python-dateutil==2.8.2 +six==1.16.0 +zipp==3.17.0 diff --git a/src/Gcode_process.py b/src/Gcode_process.py deleted file mode 100644 index 7e7f5f0..0000000 --- a/src/Gcode_process.py +++ /dev/null @@ -1,101 +0,0 @@ -import os -import numpy as np -import math -import default_Gcode -import configparser - -print_setting = configparser.ConfigParser() -#path = os.path.join(os.path.dirname(__file__), 'print_setting.ini') -#print_setting.read(path, encoding='utf-8') -print_setting.read('print_setting.ini') - -NOZZLE = float(print_setting['Nozzle']['nozzle_diameter']) -LAYER = float(print_setting['Layer']['Layer_height']) -XC = int(print_setting['Origin']['x_origin']) -YC = int(print_setting['Origin']['y_origin']) -PRINT_SPEED_DEFAULT = int(print_setting['Speed']['print_speed']) -TRAVEL_SPEED = int(print_setting['Speed']['travel_speed']) -FAN_SPEED = int(print_setting['Fan_speed']['fan_speed']) -PRINT_TEMPERATURE = int(print_setting['Temperature']['Nozzle_temperature']) -BED_TEMPERATURE = int(print_setting['Temperature']['Bed_temperature']) -EXRTRUSION_MULTIPLIER_DEFAULT = float(print_setting['Extrusion_option']['Extrusion_multiplier']) -RETRACTION = print_setting.getboolean('Travel_option','Retraction') -RETRACTION_DISTANCE = float(print_setting['Travel_option']['Retraction_distance']) -UNRETRACTION_DISTANCE = float(print_setting['Travel_option']['Unretraction_distance']) -Z_HOP = print_setting.getboolean('Travel_option','Z_hop') -Z_HOP_DISTANCE = float(print_setting['Travel_option']['Z_hop_distance']) - - - -def Gcode_export(full_object): - for layer in range(len(full_object)): - travel(full_object[layer][0][0],full_object[layer][0][1],full_object[layer][0][2]) - for segment in range(len(full_object[layer])-1): - x=full_object[layer][segment][0] - y=full_object[layer][segment][1] - z=full_object[layer][segment][2] - next_x=full_object[layer][segment+1][0] - next_y=full_object[layer][segment+1][1] - next_z=full_object[layer][segment+1][2] - Dis=math.sqrt((next_x-x)**2+(next_y-y)**2+(next_z-z)**2) - AREA=(NOZZLE-LAYER)*(LAYER)+(LAYER/2)**2*np.pi - if np.isnan(full_object[layer][segment][4]): - EXRTRUSION_MULTIPLIER = EXRTRUSION_MULTIPLIER_DEFAULT - else: - EXRTRUSION_MULTIPLIER = full_object[layer][segment][4] - - if np.isnan(full_object[layer][segment][3]): - PRINT_SPEED = PRINT_SPEED_DEFAULT - else: - PRINT_SPEED = full_object[layer][segment][3] - - Eval=4*AREA*Dis/(np.pi*1.75**2)*EXRTRUSION_MULTIPLIER - f.write(f'G1 F{PRINT_SPEED} X{next_x+XC:.5f} Y{next_y+YC:.5f} Z{next_z:.5f} E{Eval:.5f}\n') - print("Gcode exported!!") - - - -def travel(X,Y,Z): - if RETRACTION == True: - f.write(f'G1 E{-RETRACTION_DISTANCE}\n') - if Z_HOP == True: - f.write(f'G91 \n') - f.write(f'G0 Z{Z_HOP_DISTANCE}\n') - f.write(f'G90 \n') - f.write(f'G0 F{TRAVEL_SPEED} X{X+XC:.5f} Y{Y+YC:.5f} Z{Z+Z_HOP_DISTANCE:.5f}\n' ) - f.write(f'G91 \n') - f.write(f'G0 Z{-Z_HOP_DISTANCE}\n') - f.write(f'G90 \n') - else: - f.write(f'G0 F{TRAVEL_SPEED} X{X+XC:.5f} Y{Y+YC:.5f} Z{Z:.5f}\n' ) - if RETRACTION == True: - f.write(f'G1 E{UNRETRACTION_DISTANCE}\n') - -def file_remove(): - f.truncate(0) - - -def start(): - f.write(default_Gcode.startGcode) - -def set_temp(): - f.write(f'''M140 S{BED_TEMPERATURE} -M190 S{BED_TEMPERATURE} -M104 S{PRINT_TEMPERATURE} -M109 S{PRINT_TEMPERATURE} -''') - -def set_fan_speed(): - f.write(f'M106 S{FAN_SPEED}\n''') - -def end(): - f.write(default_Gcode.endGcode) - -def file_close(): - f.close() - - -f = open('G-coordinator.gcode', 'w', encoding="UTF-8") - - - diff --git a/src/modeling.py b/src/buffer/G-coordinator.gcode similarity index 100% rename from src/modeling.py rename to src/buffer/G-coordinator.gcode diff --git a/src/buffer/default_start.py b/src/buffer/default_start.py new file mode 100644 index 0000000..a9ce326 --- /dev/null +++ b/src/buffer/default_start.py @@ -0,0 +1,12 @@ +# Documentation: +# English: https://gcoordinator.readthedocs.io/en/latest/ +# Japanese: https://gcoordinator.readthedocs.io/ja/latest/ + +import gcoordinator as gc + + + + + + +gc.gui_export() \ No newline at end of file diff --git a/src/buffer/full_object.pickle b/src/buffer/full_object.pickle new file mode 100644 index 0000000..b0b5655 Binary files /dev/null and b/src/buffer/full_object.pickle differ diff --git a/src/console.py b/src/console.py new file mode 100644 index 0000000..243eb40 --- /dev/null +++ b/src/console.py @@ -0,0 +1,4 @@ +def print( message): + from window.main_window import app, main_window + main_window.print_console(str(message)) + app.processEvents() \ No newline at end of file diff --git a/src/default_Gcode.py b/src/default_Gcode.py deleted file mode 100644 index c4d2119..0000000 --- a/src/default_Gcode.py +++ /dev/null @@ -1,128 +0,0 @@ -import configparser -import os - - - -print_setting = configparser.ConfigParser() -#path = os.path.join(os.path.dirname(__file__), 'print_setting.ini') -#print_setting.read(path, encoding='utf-8') -print_setting.read('print_setting.ini') - -NOZZLE = float(print_setting['Nozzle']['nozzle_diameter']) -LAYER = float(print_setting['Layer']['Layer_height']) -XC = int(print_setting['Origin']['x_origin']) -YC = int(print_setting['Origin']['y_origin']) -PRINT_SPPED = int(print_setting['Speed']['print_speed']) -TRAVEL_SPEED = int(print_setting['Speed']['travel_speed']) -FAN_SPEED = int(print_setting['Fan_speed']['fan_speed']) -PRINT_TEMPERATURE = int(print_setting['Temperature']['Nozzle_temperature']) -BED_TEMPERATURE = int(print_setting['Temperature']['Bed_temperature']) -EXRTRUSION_MULTIPLIER = float(print_setting['Extrusion_option']['Extrusion_multiplier']) - - - -startGcode=f"""AnyCubicMega - -M201 X1250 Y1250 Z400 E5000 ; sets maximum accelerations, mm/sec^2 -M203 X180 Y180 Z12 E80 ; sets maximum feedrates, mm/sec -M204 P1250 R1250 T1250 ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2 -M205 X8.00 Y8.00 Z2.00 E10.00 ; sets the jerk limits, mm/sec -M205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec - - -G90 ; use absolute coordinates -M83 ; extruder relative mode -M104 S{PRINT_TEMPERATURE} ; set extruder temp for bed leveling -M140 S{BED_TEMPERATURE} -M109 R{PRINT_TEMPERATURE} ; wait for bed leveling temp -M190 S{BED_TEMPERATURE} -G28 ; home all without mesh bed level -G29 ; mesh bed leveling -M104 S{PRINT_TEMPERATURE} -G92 E0.0 -G1 Y-2.0 X179 F2400 -G1 Z30 F720 -M109 S{PRINT_TEMPERATURE} - -; intro line -G1 X170 F1000 -G1 Z0.2 F720 -G1 X110.0 E8.0 F900 -M73 P0 R91 -G1 X40.0 E10.0 F700 -G92 E0.0 - -M221 S100 ; set flow -G21 ; set units to millimeters -G90 ; use absolute coordinates -M83 ; use relative distances for extrusion -M900 K0 ; No linear advance - - -G92 E0.0 - -G1 Z0.200 F9000.000 - - -G1 E-3.20000 F4200.00000 -G1 Z0.400 F9000.000 -G1 X20.0 Y10.0 -G1 Z0.300 -G1 E3.20000 F2400.00000 - - -M106 S{FAN_SPEED} ; set fan speed - -;END OF THE START GCODE - - -""" - - - - -endGcode=""" - -;START OF THE END GCODE - -G1 F2400 E-6 -M140 S0 -M204 S4000 -M205 X20 Y20 -M107 -M104 S0 ; turn off extruder -M140 S0 ; turn off bed -M84 ; disable motors -M107 -G91 ;relative positioning -G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure -G1 Z+0.5 E-5 ;move Z up a bit and retract filament even more -G28 X0 ;Y0 ;move X/Y to min endstops, so the head is out of the way -G1 Y180 F2000 -M84 ;steppers off -G90 -M300 P300 S4000 -M82 ;absolute extrusion mode -M104 S0 -;End of Gcode -;SETTING_3 {"extruder_quality": ["[general]\\nversion = 4\\nname = Normal 1.0 no -;SETTING_3 zzle vase mode\\ndefinition = anycubic_i3_mega\\n\\n[metadata]\\ninte -;SETTING_3 nt_category = default\\nquality_type = normal\\nposition = 0\\nsettin -;SETTING_3 g_version = 14\\ntype = quality_changes\\n\\n[values]\\ninfill_sparse -;SETTING_3 _density = 5\\nline_width = 1.0\\nmaterial_print_temperature = 220\\n -;SETTING_3 skirt_brim_speed = 20\\nspeed_layer_0 = 10\\nspeed_print = 20\\nspeed -;SETTING_3 _topbottom = 60\\nspeed_wall_0 = 30\\nspeed_wall_x = 30\\ntop_bottom_ -;SETTING_3 pattern = lines\\ntop_bottom_thickness = 0.8\\nwall_thickness = 0.8\\ -;SETTING_3 n\\n"], "global_quality": "[general]\\nversion = 4\\nname = Normal 1. -;SETTING_3 0 nozzle vase mode\\ndefinition = anycubic_i3_mega\\n\\n[metadata]\\n -;SETTING_3 quality_type = normal\\nsetting_version = 14\\ntype = quality_changes -;SETTING_3 \\n\\n[values]\\nmagic_spiralize = True\\nmaterial_bed_temperature = -;SETTING_3 65\\nsupport_enable = False\\n\\n"} - - - - -""" - - - diff --git a/src/draw_object.py b/src/draw_object.py deleted file mode 100644 index 3ccbe1b..0000000 --- a/src/draw_object.py +++ /dev/null @@ -1,32 +0,0 @@ -import numpy as np -import pyqtgraph.opengl as gl -import pyqtgraph as pg -import copy - - - -def draw_object_array(full_object,widget,slider): - pos_array = copy.deepcopy(full_object) - for layer_list in pos_array: - for segment in layer_list: - if len(segment) == 5 or len(segment)==4: - del segment[3:] - else: - pass - - if slider==len(pos_array): - for i in range(slider): - if len(pos_array[i])==1: - continue - else: - plt = gl.GLLinePlotItem(pos = pos_array[i] ,color = pg.intColor(pos_array[i][0][2],300,alpha=180),width=0.5, antialias = True) - widget.addItem(plt) - - else: - for i in range(slider): - if i == slider-1: - plt = gl.GLLinePlotItem(pos = pos_array[i] ,color = pg.mkColor('w'),width=0.8, antialias = True) - widget.addItem(plt) - else: - plt = gl.GLLinePlotItem(pos = pos_array[i] ,color = pg.intColor(pos_array[i][0][2],300,alpha=80),width=0.8, antialias = True) - widget.addItem(plt) \ No newline at end of file diff --git a/src/gcode_modeling.ui b/src/gcode_modeling.ui deleted file mode 100644 index a117e2b..0000000 --- a/src/gcode_modeling.ui +++ /dev/null @@ -1,363 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1264 - 766 - - - - - 0 - 0 - - - - MainWindow - - - - true - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 0 - - - - - - - - - Open File - - - - - - - Save - - - - - - - Save As - - - - - - - - - - - - reload - - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - Qt::Vertical - - - - - - - ... - - - - - - - ... - - - - - - - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - - 250 - 540 - - - - - - - - - 0 - 0 - - - - Gcode Export - - - - - - - - - - - - 0 - 0 - 1264 - 24 - - - - - - toolBar - - - TopToolBarArea - - - false - - - - - toolBar_2 - - - TopToolBarArea - - - false - - - - - - opengl.GLViewWidget - QGraphicsView -
pyqtgraph
-
- - ParameterTree - QGraphicsView -
pyqtgraph.parametertree
-
-
- - - - open_button - pressed() - MainWindow - file_open() - - - 118 - 61 - - - 175 - 39 - - - - - save_button - pressed() - MainWindow - file_save() - - - 213 - 61 - - - 268 - 45 - - - - - save_as_button - pressed() - MainWindow - file_save_as() - - - 317 - 71 - - - 348 - 46 - - - - - reload_button - pressed() - MainWindow - save_as_modeling() - - - 233 - 513 - - - 349 - 439 - - - - - reload_button - pressed() - MainWindow - draw_updated_object() - - - 130 - 520 - - - 343 - 439 - - - - - gcode_export_button - pressed() - MainWindow - Gcode_create() - - - 1034 - 599 - - - 761 - 439 - - - - - Slider - valueChanged(int) - MainWindow - redraw_object() - - - 898 - 186 - - - 476 - 206 - - - - - up_button - pressed() - MainWindow - up_button_pressed() - - - 907 - 577 - - - 477 - 367 - - - - - down_button - pressed() - MainWindow - down_button_pressed() - - - 914 - 604 - - - 475 - 401 - - - - - - setTextHelloWorld() - file_open() - file_save() - file_save_as() - save_as_modeling() - draw_updated_object() - Gcode_create() - redraw_object() - up_button_pressed() - down_button_pressed() - -
diff --git a/src/gcode_modeling_ui.py b/src/gcode_modeling_ui.py deleted file mode 100644 index e8a20d4..0000000 --- a/src/gcode_modeling_ui.py +++ /dev/null @@ -1,175 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'gcode_modeling.ui' -# -# Created by: PyQt5 UI code generator 5.15.7 -# -# WARNING: Any manual changes made to this file will be lost when pyuic5 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt5 import QtCore, QtGui, QtWidgets,Qt -from PyQt5.QtWidgets import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * -from PyQt5.QtPrintSupport import * -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(1264, 766) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) - MainWindow.setSizePolicy(sizePolicy) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setEnabled(True) - self.centralwidget.setObjectName("centralwidget") - self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralwidget) - self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.frame = QtWidgets.QFrame(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) - self.frame.setSizePolicy(sizePolicy) - self.frame.setObjectName("frame") - self.verticalLayout = QtWidgets.QVBoxLayout(self.frame) - self.verticalLayout.setObjectName("verticalLayout") - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.open_button = QtWidgets.QPushButton(self.frame) - self.open_button.setObjectName("open_button") - self.horizontalLayout.addWidget(self.open_button) - self.save_button = QtWidgets.QPushButton(self.frame) - self.save_button.setObjectName("save_button") - self.horizontalLayout.addWidget(self.save_button) - self.save_as_button = QtWidgets.QPushButton(self.frame) - self.save_as_button.setObjectName("save_as_button") - self.horizontalLayout.addWidget(self.save_as_button) - self.verticalLayout.addLayout(self.horizontalLayout) - - - self.editor = PlainTextEdit(self.frame) - self.editor.setObjectName("editor") - self.editor.setLineWrapMode(PlainTextEdit.LineWrapMode.NoWrap) - self.verticalLayout.addWidget(self.editor) - self.reload_button = QtWidgets.QPushButton(self.frame) - self.reload_button.setObjectName("reload_button") - self.verticalLayout.addWidget(self.reload_button) - self.message_console = QtWidgets.QTextEdit(self.frame) - self.message_console.setObjectName("message_console") - self.verticalLayout.addWidget(self.message_console) - self.verticalLayout.setStretch(1, 10) - self.verticalLayout.setStretch(3, 1) - self.horizontalLayout_2.addWidget(self.frame) - self.graphicsView = opengl.GLViewWidget(self.centralwidget) - self.graphicsView.setObjectName("graphicsView") - self.horizontalLayout_2.addWidget(self.graphicsView) - self.frame1 = QtWidgets.QFrame(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame1.sizePolicy().hasHeightForWidth()) - self.frame1.setSizePolicy(sizePolicy) - self.frame1.setObjectName("frame1") - self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.frame1) - self.verticalLayout_3.setObjectName("verticalLayout_3") - self.Slider = QtWidgets.QSlider(self.frame1) - self.Slider.setOrientation(QtCore.Qt.Vertical) - self.Slider.setObjectName("Slider") - self.verticalLayout_3.addWidget(self.Slider) - self.up_button = QtWidgets.QToolButton(self.frame1) - self.up_button.setObjectName("up_button") - self.verticalLayout_3.addWidget(self.up_button) - self.down_button = QtWidgets.QToolButton(self.frame1) - self.down_button.setObjectName("down_button") - self.verticalLayout_3.addWidget(self.down_button) - self.horizontalLayout_2.addWidget(self.frame1) - self.frame2 = QtWidgets.QFrame(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame2.sizePolicy().hasHeightForWidth()) - self.frame2.setSizePolicy(sizePolicy) - self.frame2.setObjectName("frame2") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame2) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.parameter_tree = ParameterTree(self.frame2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.parameter_tree.sizePolicy().hasHeightForWidth()) - self.parameter_tree.setSizePolicy(sizePolicy) - self.parameter_tree.setMinimumSize(QtCore.QSize(250, 540)) - self.parameter_tree.setObjectName("parameter_tree") - self.verticalLayout_2.addWidget(self.parameter_tree) - self.gcode_export_button = QtWidgets.QPushButton(self.frame2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.gcode_export_button.sizePolicy().hasHeightForWidth()) - self.gcode_export_button.setSizePolicy(sizePolicy) - self.gcode_export_button.setObjectName("gcode_export_button") - self.verticalLayout_2.addWidget(self.gcode_export_button) - self.horizontalLayout_2.addWidget(self.frame2) - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1264, 24)) - self.menubar.setObjectName("menubar") - MainWindow.setMenuBar(self.menubar) - self.toolBar = QtWidgets.QToolBar(MainWindow) - self.toolBar.setObjectName("toolBar") - MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) - self.toolBar_2 = QtWidgets.QToolBar(MainWindow) - self.toolBar_2.setObjectName("toolBar_2") - MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_2) - - self.retranslateUi(MainWindow) - self.open_button.pressed.connect(MainWindow.file_open) # type: ignore - self.save_button.pressed.connect(MainWindow.file_save) # type: ignore - self.save_as_button.pressed.connect(MainWindow.file_save_as) # type: ignore - self.reload_button.pressed.connect(MainWindow.save_as_modeling) # type: ignore - self.reload_button.pressed.connect(MainWindow.draw_updated_object) # type: ignore - self.gcode_export_button.pressed.connect(MainWindow.Gcode_create) # type: ignore - self.Slider.valueChanged['int'].connect(MainWindow.redraw_object) # type: ignore - self.up_button.pressed.connect(MainWindow.up_button_pressed) # type: ignore - self.down_button.pressed.connect(MainWindow.down_button_pressed) # type: ignore - QtCore.QMetaObject.connectSlotsByName(MainWindow) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.open_button.setText(_translate("MainWindow", " Open File ")) - self.save_button.setText(_translate("MainWindow", "Save")) - self.save_as_button.setText(_translate("MainWindow", "Save As")) - self.reload_button.setText(_translate("MainWindow", "reload")) - self.up_button.setText(_translate("MainWindow", "...")) - self.down_button.setText(_translate("MainWindow", "...")) - self.gcode_export_button.setText(_translate("MainWindow", "Gcode Export")) - self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar")) - self.toolBar_2.setWindowTitle(_translate("MainWindow", "toolBar_2")) - -class PlainTextEdit(QPlainTextEdit): - def keyPressEvent(self, event): - #オートインデントの処理 - if event.key() in (Qt.Key_Return, Qt.Key_Enter): - - indent_width = 4 - line_number = self.textCursor().blockNumber() - #print(line_number) - line_text = self.document().findBlockByLineNumber(line_number).text() - indent_level = line_text.count(" " * indent_width) - if line_text.endswith(":"): - indent_level += 1 - self.insertPlainText("\n") - self.insertPlainText( " " * indent_width * indent_level) - - - return - super(PlainTextEdit, self).keyPressEvent(event) - -from pyqtgraph import opengl -from pyqtgraph.parametertree import ParameterTree diff --git a/src/import_file.py b/src/import_file.py deleted file mode 100644 index fd99782..0000000 --- a/src/import_file.py +++ /dev/null @@ -1,71 +0,0 @@ -''' -import_file - -import_file is meant to import a python script from a normal file path. -Relative (dotted) imports are complicated, and fixing sys.path just feels wrong. - -Usage examples:: - - >>>from import_file import import_file - >>>mylib = import_file('c:\\mylib.py') - >>>another = import_file('relative_subdir/another.py') - - -So now you aren't limited to importing only within your package or trail of -__init__.py files. - -This should work for python 2.5-3.2 and it's public domain, have fun. - - - - http://uberpython.wordpress.com - - http://code.google.com/p/import-file/ - - http://pypi.python.org/pypi/import_file - - ubershmekel at gmail - -''' - -import imp as _imp -import os as _os - - -def import_file(fpath): - ''' - fpath - the relative or absolute path to the .py file which is imported. - - Returns the imported module. - - NOTE: if import_file is called twice with the same module, the module is reloaded. - ''' - if hasattr(_os, 'getcwdu'): - # python 2 returns question marks in os.path.realpath for - # ascii input (eg '.'). - original_path = _os.path.realpath(_os.getcwdu()) - else: - original_path = _os.path.realpath(_os.path.curdir) - dst_path = _os.path.dirname(fpath) - if dst_path == '': - dst_path = '.' - - # remove the .py suffix - script_name = _os.path.basename(fpath) - if script_name.endswith('.py'): - mod_name = script_name[:-3] - else: - # packages for example. - mod_name = script_name - - _os.chdir(dst_path) - fhandle = None - try: - tup = _imp.find_module(mod_name, ['.']) - module = _imp.load_module(mod_name, *tup) - fhandle = tup[0] - finally: - _os.chdir(original_path) - if fhandle is not None: - fhandle.close() - - return module - - - diff --git a/src/main.py b/src/main.py index a1fc81f..484f783 100644 --- a/src/main.py +++ b/src/main.py @@ -1,329 +1,37 @@ -import sys -import os -import traceback -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * -from PyQt5.QtPrintSupport import * -from pyqtgraph.parametertree import Parameter -import pyqtgraph.opengl as gl -import modeling -import importlib -from import_file import import_file -import syntax_pars -import Gcode_process -import draw_object -import configparser -from gcode_modeling_ui import Ui_MainWindow -import print_functions +''' +G-coordinator ver 3.0.0 +License: MIT +Author: Tomohiro Taniguchi +- Documentation(Japanese): https://gcoordinator.readthedocs.io/ja/latest/ +- Documentation(English): https://gcoordinator.readthedocs.io/en/latest/ +- GitHub Repository: https://github.com/tomohiron907/gcoordinator +''' -class MainWindow(QMainWindow, Ui_MainWindow): - def __init__(self, parent=None): - super(MainWindow, self).__init__(parent) - self.setupUi(self) - self.initUI() - self.grid_draw() - - def initUI(self): - self.editor.setStyleSheet("""QPlainTextEdit{ - color: #ccc; - background-color: #2b2b2b;}""") - #シンタックス表示 - self.highlight=syntax_pars.PythonHighlighter(self.editor.document()) - self.graphicsView.setCameraPosition(distance=120) - self.message_console.setReadOnly(True) - self.message_console.setStyleSheet("background-color: rgb(26, 26, 26);") - self.up_button.setArrowType(Qt.UpArrow) - self.down_button.setArrowType(Qt.DownArrow) - self.read_setting() - self.parameter_tree_setting() - self.p = Parameter.create(name='params', type='group', children=self.params) - self.p.sigTreeStateChanged.connect(self.change) - self.parameter_tree.setParameters(self.p, showTop=True) - self.parameter_tree.resize(250,540) - - # drawing grid in pyqtgraph widget - def grid_draw(self): - gz = gl.GLGridItem() - gz.setSize(200,200) - gz.setSpacing(10,10) - axis = gl.GLAxisItem() - axis.setSize(50,50,50) - x_text = gl.GLTextItem() - x_text.setData(pos=(50,0,0),text = 'x') - y_text = gl.GLTextItem() - y_text.setData(pos=(0,50,0),text = 'y') - z_text = gl.GLTextItem() - z_text.setData(pos=(0,0,50),text = 'z') - self.graphicsView.addItem(axis) - self.graphicsView.addItem(x_text) - self.graphicsView.addItem(y_text) - self.graphicsView.addItem(z_text) - self.graphicsView.addItem(gz) - - # reload and draw update object in pyqtgraph widget - def draw_updated_object(self): - print('draw_object') - self.graphicsView.clear() # initialize pyqtgraph widget - self.grid_draw() - try: - #importlib.reload(modeling) #reload updated modeling.py - modeling = import_file('modeling.py') - self.full_object=modeling.object_modeling() # get the list of coordinate from modeling.py - self.message_console.setTextColor(QColor('#00bfff')) - self.message_console.setText('object displyed') - with open("modeling.py",'w') as f: - pass - except: - print('syntax error!!') - self.message_console.setTextColor(QColor('#FF6347')) - print(str(traceback.format_exc())) - self.message_console.setText(traceback.format_exc()) - with open("modeling.py",'w') as f: - pass - draw_object.draw_object_array(self.full_object,self.graphicsView,len(self.full_object)) #redraw updated full objects in modeling.py - self.Slider.setRange(0, len(self.full_object)) # set slider range - self.file_save() +#Required Libraries: +#pip install -r requirements.txt - def Gcode_create(self): - importlib.reload(Gcode_process) - Gcode_process.file_remove() - Gcode_process.start() - Gcode_process.set_temp() - Gcode_process.set_fan_speed() - Gcode_process.Gcode_export(self.full_object) - Gcode_process.end() - Gcode_process.file_close() - self.message_console.setTextColor(QColor('#00bfff')) - self.message_console.setText('Gcode Exported') - self.gcode_window = GcodeExportWindow() - self.gcode_window.show() - - def redraw_object(self): - #print(self.Slider.value()) - self.graphicsView.clear() # initialize pyqtgraph widget - self.grid_draw() - draw_object.draw_object_array(self.full_object,self.graphicsView,self.Slider.value()) #redraw updated objects in modeling.py - #print(value) - - def up_button_pressed(self): - self.Slider.setValue(self.Slider.value()+1) - self.redraw_object() - - def down_button_pressed(self): - self.Slider.setValue(self.Slider.value()-1) - self.redraw_object() - - def file_open(self): - # getting path and bool value - path, _ = QFileDialog.getOpenFileName(self, "Open file", "", - "Text documents (*.txt);All files (*.*)") - # if path is true - if path: - # try opening path - try: - with open(path, 'r') as f: - # read the file - text = f.read() - # if some error occurred - except Exception as e: - # show error using critical method - self.dialog_critical(str(e)) - # else - else: - # update path value - self.path = path - # update the text - self.editor.setPlainText(text) - # update the title - self.update_title() +#To execute the G-coordinator, follow these steps: +#1. Change the current directory to "src" by using the command: `cd src`. +#2. Run the "main.py" file by executing the command: `python3 main.py`. - def save_as_modeling(self): - # get the text - text = self.editor.toPlainText() - # try catch block - # opening file to write - with open('modeling.py', 'w') as f: - # write text in the file - f.write(text) - #self.draw_object() - def file_save(self): - # if there is no save path - if self.path is None: - # call save as method - return self.file_save_as() - # else call save to path method - self._save_to_path(self.path) - # action called by save as action - - def file_save_as(self): - # opening path - path, _ = QFileDialog.getSaveFileName(self, "Save file", "", - "Text documents (*.txt);All files (*.*)") - # if dialog is cancelled i.e no path is selected - if not path: - # return this method - # i.e no action performed - return - - # else call save to path method - self._save_to_path(path) - - # save to path method - def _save_to_path(self, path): - # get the text - text = self.editor.toPlainText() - # try catch block - try: - # opening file to write - with open(path, 'w') as f: - # write text in the file - f.write(text) - - # if error occurs - except Exception as e: - # show error using critical - self.dialog_critical(str(e)) - - # else do this - else: - # change path - self.path = path - # update the title - self.update_title() - - def update_title(self): - # setting window title with prefix as file name - # suffix aas PyQt5 Notepad - self.setWindowTitle("%s - G-coordinator" %(os.path.basename(self.path) - if self.path else "Untitled")) - - - - def read_setting(self): - self.print_setting = configparser.ConfigParser() - #path = os.path.join(os.path.dirname(__file__), 'print_setting.ini') - #elf.print_setting.read(path, encoding='utf-8') - self.print_setting.read('print_setting.ini') +import sys +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtPrintSupport import * +from window.main_window import main_window, app - def parameter_tree_setting(self): - self.params = [ - {'name': 'Nozzle', 'type': 'group', 'children': [ - {'name': 'Nozzle_diameter', 'type': 'float', 'value': float(self.print_setting['Nozzle']['Nozzle_diameter'])}, - ]}, - {'name': 'Layer', 'type': 'group', 'children': [ - {'name': 'Layer_height', 'type': 'float', 'value': float(self.print_setting['Layer']['Layer_height'])}, - ]}, - {'name': 'Origin', 'type': 'group', 'children': [ - {'name': 'X_origin', 'type': 'int', 'value': int(self.print_setting['Origin']['X_origin'])}, - {'name': 'Y_origin', 'type': 'int', 'value': int(self.print_setting['Origin']['Y_origin'])}, - ]}, - {'name': 'Speed', 'type': 'group', 'children': [ - {'name': 'Print_speed', 'type': 'int', 'value': int(self.print_setting['Speed']['Print_speed'])}, - {'name': 'Travel_speed', 'type': 'int', 'value': int(self.print_setting['Speed']['Travel_speed'])}, - ]}, - {'name': 'Fan_speed', 'type': 'group', 'children': [ - {'name': 'Fan_speed', 'type': 'int', 'value': int(self.print_setting['Fan_speed']['fan_speed'])}, - ]}, - {'name': 'Temperature', 'type': 'group', 'children': [ - {'name': 'Nozzle_temperature', 'type': 'int', 'value': int(self.print_setting['Temperature']['Nozzle_temperature'])}, - {'name': 'Bed_temperature', 'type': 'int', 'value': int(self.print_setting['Temperature']['Bed_temperature'])}, - ]}, - {'name': 'Travel_option', 'type': 'group', 'children': [ - {'name': 'Retraction', 'type': 'bool', 'value': self.print_setting.getboolean('Travel_option','Retraction')}, - {'name': 'Retraction_distance', 'type': 'float', 'value': float(self.print_setting['Travel_option']['Retraction_distance'])}, - {'name': 'Unretraction_distance', 'type': 'float', 'value': float(self.print_setting['Travel_option']['Unretraction_distance'])}, - {'name': 'Z_hop', 'type': 'bool', 'value': self.print_setting.getboolean('Travel_option','Z_hop')}, - {'name': 'Z_hop_distance', 'type': 'float', 'value': float(self.print_setting['Travel_option']['Z_hop_distance'])}, - ]}, - {'name': 'Extrusion_option', 'type': 'group', 'children': [ - {'name': 'Extrusion_multiplier', 'type': 'float', 'value': float(self.print_setting['Extrusion_option']['Extrusion_multiplier'])}, - ]}, - - ] +def launch(): + icon_path = '../img/G-coordinator.png' + app_icon = QIcon(icon_path) + app.setWindowIcon(app_icon) - - def change(self, param, changes): - print("tree changes:") - for param, change, data in changes: - path = self.p.childPath(param) - childName = '.'.join(path) - - self.print_setting.set(path[0],path[1],str(data)) - with open('print_setting.ini', 'w') as file: - self.print_setting.write(file) - print(' parameter: %s'% childName) - print(' change: %s'% change) - print(' data: %s'% str(data)) - print(' ----------') - - - -class GcodeExportWindow(QWidget): - - def __init__(self): - super().__init__() - self.title = 'Save G-code file' - self.left = 10 - self.top = 10 - self.width = 500 - self.height = 400 - self.initUI() - - def initUI(self): - self.setWindowTitle(self.title) - self.setGeometry(self.left, self.top, self.width, self.height) - layout = QVBoxLayout() - self.button = QPushButton('Save G-code file', self) - self.button.setToolTip('Click to save G-code file') - #button.move(200, 20) - self.button.clicked.connect(self.saveFileDialog) - - self.textEdit = QTextEdit(self) - self.textEdit.setFont(QFont("Courier New", 10)) - #self.textEdit.setGeometry(10, 60, 480, 330) - self.showGCodePreview() - - self.label = QLabel(self) - #self.label.setGeometry(10, 350, 480, 40) - self.label.setFont(QFont("Arial", 10, QFont.Bold)) - self.label.setText("Showing the first 1000 lines of G-code file.") - - layout.addWidget(self.button) - layout.addWidget(self.label) - layout.addWidget(self.textEdit) - self.setLayout(layout) - self.show() - - def showGCodePreview(self): - with open("G-coordinator.gcode", 'r') as f: - content = f.readlines() - if len(content) > 1000: - content = content[:1000] - content = ''.join(content) - self.textEdit.setPlainText(content) - - def saveFileDialog(self): - options = QFileDialog.Options() - options |= QFileDialog.DontUseNativeDialog - fileName, _ = QFileDialog.getSaveFileName(self,"Save G-code file","","G-code Files (*.gcode);;All Files (*)", options=options) - if fileName: - with open("G-coordinator.gcode", 'r') as f: - content = f.read() - with open(fileName, 'w') as new_f: - new_f.write(content) - self.showGCodePreview() - - + main_window.show() + sys.exit(app.exec_()) if __name__ == '__main__': - app = QApplication(sys.argv) - '''path = os.path.join(os.path.dirname(sys.modules[__name__].__file__), 'layers.png') - app.setWindowIcon(QIcon(path))''' - main_window = MainWindow() - main_window.show() #ウィンドウの表示 - sys.exit(app.exec_()) #プログラム終了 + launch() \ No newline at end of file diff --git a/src/modeling_toolbox/function_to_path.py b/src/modeling_toolbox/function_to_path.py new file mode 100644 index 0000000..d91950c --- /dev/null +++ b/src/modeling_toolbox/function_to_path.py @@ -0,0 +1,35 @@ +import numpy as np +import math +import print_settings +from path_generator import * +import matplotlib.pyplot as plt + + + +def eq_to_path(fn,height, x_min=-10, x_max=10, y_min=-10, y_max=10, z_min=0, z_max=10, x_resolution = 200, y_resolution = 200, z_resolution = 200, val = 0): + + + x = np.linspace(x_min , x_max , x_resolution) + y = np.linspace(y_min , y_max , y_resolution) + z = np.linspace(z_min , z_max , z_resolution) + + + # Equation for the Gyroid surface + #equation = np.sin(X) * np.cos(Y) + np.sin(Y) * np.cos(Z) + np.sin(Z) * np.cos(X) + X, Y, Z = np.meshgrid(x, y, z) + equation = fn(X, Y, Z) + path_list_buffer = [] + slice_plane = equation[:, :, height] + contours = plt.contour(x, y, slice_plane, levels=[0], colors='black') + for contour in contours.collections: + paths = contour.get_paths() + for path in paths: + points = path.vertices + x_coords = points[:, 0] + y_coords = points[:, 1] + z_coords = np.full_like(x_coords, height*0.2) + wall = Path(x_coords, y_coords, z_coords) + path_list_buffer.append(wall) + return PathList(path_list_buffer) + + diff --git a/src/modeling_toolbox/stl_slice.py b/src/modeling_toolbox/stl_slice.py new file mode 100644 index 0000000..efd4e6a --- /dev/null +++ b/src/modeling_toolbox/stl_slice.py @@ -0,0 +1,30 @@ +import trimesh +import matplotlib.pyplot as plt +import path_generator + +import numpy as np +# load mesh +#mesh = trimesh.load('cylinder_surface.stl', merge_norm=True, merge_tex=True) + +# slice the mesh +def slice(mesh, z_height): + + slice = mesh.section_multiplane(plane_origin=[0, 0, 0], plane_normal=[0, 0, 1], heights=[z_height]) + + s = slice[0] + path_num = len(s.discrete) + path_list = [] + for i in range(path_num): + points = s.discrete[i] + plt.plot(*points.T) + + coords = points.T + x = np.array(coords[0]) + y = np.array(coords[1]) + z = np.full_like(x, z_height) + path = path_generator.Path(x, y, z) + path_list.append(path) + + path_list = path_generator.PathList(path_list) + return path_list + diff --git a/src/print_functions.py b/src/print_functions.py deleted file mode 100644 index 1311f3e..0000000 --- a/src/print_functions.py +++ /dev/null @@ -1,124 +0,0 @@ -import numpy as np -import math -#from shapely.geometry.polygon import LinearRing,LineString -import sys - -def print_layer(x, y, z, Feed = None, E_multiplier = None): - coordinates = np.column_stack([x,y,z]).tolist() - - if Feed is None: - nans = np.zeros(len(coordinates)) - nans[:] = np.nan - Feed_list = nans - elif type(Feed) is np.ndarray: - Feed_list = Feed - else: - Feed_list = np.full(len(coordinates),Feed) - - - if E_multiplier is None: - nans = np.zeros(len(coordinates)) - nans[:] = np.nan - E_multiplier_list = nans - elif type(E_multiplier) is np.ndarray: - E_multiplier_list = E_multiplier - else: - E_multiplier_list = np.full(len(coordinates),E_multiplier) - - layer_list = np.column_stack([x,y,z,Feed_list,E_multiplier_list]).tolist() - - return layer_list - -def travel_to(x, y, z, Feed = None, E_multiplier = None): - coordinates = np.column_stack([x,y,z]).tolist() - - if Feed is None: - nans = np.zeros(len(coordinates)) - nans[:] = np.nan - Feed_list = nans - elif type(Feed) is np.ndarray: - Feed_list = Feed - else: - Feed_list = np.full(len(coordinates),Feed) - - - if E_multiplier is None: - nans = np.zeros(len(coordinates)) - nans[:] = np.nan - E_multiplier_list = nans - elif type(E_multiplier) is np.ndarray: - E_multiplier_list = E_multiplier - else: - E_multiplier_list = np.full(len(coordinates),E_multiplier) - - layer_list = np.column_stack([x,y,z,Feed_list,E_multiplier_list]).tolist() - - return layer_list - - - -def line_fill(a,distance,angle): - x=[] - y = [] - - for i in range(len(a)): - x.append(a[i][0]) - y.append(a[i][1]) - - - x=np.array(x) - y= np.array(y) - if angle == np.pi/2 or angle == -np.pi/2: - angle -=0.001 - y_intersept = distance / math.cos(angle) - K = np.arange(-200/math.cos(angle),200/math.cos(angle),y_intersept) - Xlist=[] - Ylist=[] - - - slope = math.tan(angle) - for k in K: - for n in range(len(a)-1): - if (slope*x[n+1] - y[n+1] + k )* (slope*x[n] - y [n] +k) < 0: - X=(x[n+1]*y[n]-y[n+1]*x[n]-(x[n+1]-x[n])*k)/(slope*(x[n+1]-x[n])-(y[n+1]-y[n])) - Y=slope*X+k - Xlist.append(X) - Ylist.append(Y) - - - for i in range(len(Xlist)-1): - if i%4==2: - Xlist[i],Xlist[i+1]=Xlist[i+1],Xlist[i] - Ylist[i],Ylist[i+1]=Ylist[i+1],Ylist[i] - - - for i in range(len(Xlist)-1): - if i%2==0: - if (slope*Xlist[i]-Ylist[i])<(slope*x[0]-y[0]): - Xlist[i],Xlist[i+1]=Xlist[i+1],Xlist[i] - Ylist[i],Ylist[i+1]=Ylist[i+1],Ylist[i] - Zlist = [a[0][2] for i in range(len(Xlist))] - return Xlist,Ylist,Zlist - - -'''def contour_offset(layer,distance): - x=[] - y = [] - z = [] - for i in range(len(layer)): - x.append(layer[i][0]) - y.append(layer[i][1]) - z.append(layer[i][2]) - x=np.array(x) - y= np.array(y) - z = np.array(z) - pos_array = np.column_stack([x, y, z]) - poly_line = LineString(pos_array) - poly_line_offset = poly_line.parallel_offset(distance, side='left', resolution=6, join_style=2, mitre_limit=1) - Xlist = poly_line_offset.xy[0] - Ylist = poly_line_offset.xy[1] - Zlist = [pos_array[0][2] for i in range(len(Xlist))] - offset_list = np.column_stack([Xlist,Ylist,Zlist]) - return Xlist,Ylist,Zlist''' - - diff --git a/src/print_setting.ini b/src/print_setting.ini deleted file mode 100644 index 36536dc..0000000 --- a/src/print_setting.ini +++ /dev/null @@ -1,31 +0,0 @@ -[Nozzle] -nozzle_diameter = 1.0 - -[Layer] -layer_height = 1.0 - -[Origin] -x_origin = 105 -y_origin = 105 - -[Speed] -print_speed = 400 -travel_speed = 5000 - -[Fan_speed] -fan_speed = 255 - -[Temperature] -nozzle_temperature = 180 -bed_temperature = 50 - -[Travel_option] -retraction = False -retraction_distance = 3.5 -unretraction_distance = 4.0 -z_hop = False -z_hop_distance = 2 - -[Extrusion_option] -extrusion_multiplier = 1.0 - diff --git a/src/resources/FiraCode-Regular.ttf b/src/resources/FiraCode-Regular.ttf new file mode 100644 index 0000000..bd73685 Binary files /dev/null and b/src/resources/FiraCode-Regular.ttf differ diff --git a/src/resources/open_file.svg b/src/resources/open_file.svg new file mode 100644 index 0000000..b2ea7d1 --- /dev/null +++ b/src/resources/open_file.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/resources/play.svg b/src/resources/play.svg new file mode 100644 index 0000000..0fd91b1 --- /dev/null +++ b/src/resources/play.svg @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/src/settings/app_settings.ini b/src/settings/app_settings.ini new file mode 100644 index 0000000..be060d4 --- /dev/null +++ b/src/settings/app_settings.ini @@ -0,0 +1,8 @@ +[General] +theme=dark + +[console] +font_size=15 + +[editor] +font_size=15 diff --git a/src/settings/end_gcode.txt b/src/settings/end_gcode.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/settings/settings.json b/src/settings/settings.json new file mode 100644 index 0000000..687e184 --- /dev/null +++ b/src/settings/settings.json @@ -0,0 +1,64 @@ +{ + "Print": { + "nozzle": { + "nozzle_diameter": 0.4, + "filament_diameter": 1.75 + }, + "layer": { + "layer_height": 0.2 + }, + "speed": { + "print_speed": 15000, + "travel_speed": 25000 + }, + "origin": { + "x": 100, + "y": 100 + }, + "fan_speed": { + "fan_speed": 255 + }, + "temperature": { + "nozzle_temperature": 200, + "bed_temperature": 50 + }, + "travel_option": { + "retraction": false, + "retraction_distance": 2.0, + "unretraction_distance": 2.0, + "z_hop": false, + "z_hop_distance": 3 + }, + "extrusion_option": { + "extrusion_multiplier": 1.0 + } + }, + "Hardware": { + "kinematics": "Cartesian", + "bed_size": { + "bed_size_x": 210, + "bed_size_y": 210, + "bed_size_z": 205 + } + }, + "Kinematics": { + "NozzleTilt": { + "tilt_code": "B", + "rot_code": "A", + "tilt_offset": 0.0, + "rot_offset": 0.0 + }, + "BedTiltBC": { + "tilt_code": "B", + "rot_code": "C", + "tilt_offset": 0.0, + "rot_offset": 0, + "div_distance": 0.5 + }, + "BedRotate": { + "rot_code": "C", + "rot_offset": 0.0, + "div_distance": 0.5 + } + } +} \ No newline at end of file diff --git a/src/settings/start_gcode.txt b/src/settings/start_gcode.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/setup.py b/src/setup.py deleted file mode 100644 index f32df23..0000000 --- a/src/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -This is a setup.py script generated by py2applet - -Usage: - python setup.py py2app -""" - -from setuptools import setup - -APP = ['main.py'] -DATA_FILES = ['print_setting.ini','G-coordinator.gcode'] -OPTIONS = { - 'iconfile' : 'G-coordinator.png' -} - -setup( - app=APP, - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) - - - -setup( - app=APP, - name = 'G-coordinator', - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) diff --git a/src/window/app_settings_window.py b/src/window/app_settings_window.py new file mode 100644 index 0000000..edd41af --- /dev/null +++ b/src/window/app_settings_window.py @@ -0,0 +1,97 @@ +import sys +from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QVBoxLayout, QHBoxLayout, QColorDialog, QPushButton, QFontDialog, QDialog, QRadioButton +from PyQt5.QtCore import QSettings, Qt + + +class SettingsWindow(QDialog): + """G-coordinator application settings window + + Args: + QDialog (_type_): _description_ + """ + def __init__(self): + super().__init__() + self.initUI() + + def initUI(self): + self.setWindowTitle('Basic Settings') + + # Theme Selection + theme_label = QLabel('Theme:') + self.dark_theme_radio = QRadioButton('Dark') + self.light_theme_radio = QRadioButton('Light') + + # Font Display Label + self.font_label = QLabel() + + # Font Size Setting + font_size_label = QLabel('Font Size:') + self.font_size_lineedit = QLineEdit() + + # Console Font Size Setting + console_font_size_label = QLabel('Console Font Size:') + self.console_font_size_lineedit = QLineEdit() + + # Save Button + save_button = QPushButton('Save') + save_button.clicked.connect(self.saveSettings) + + # Layout Setup + layout = QVBoxLayout() + layout.addWidget(theme_label) + layout.addWidget(self.dark_theme_radio) + layout.addWidget(self.light_theme_radio) + layout.addWidget(font_size_label) + layout.addWidget(self.font_size_lineedit) + layout.addWidget(console_font_size_label) + layout.addWidget(self.console_font_size_lineedit) + layout.addWidget(save_button) + + self.setLayout(layout) + self.show() + + # Load saved settings + self.loadSettings() + + def selectEditorFont(self): + font, ok = QFontDialog.getFont() + if ok: + self.font_label.setText(font.family()) + + + + def saveSettings(self): + # Save the input settings + settings = QSettings('settings/app_settings.ini', QSettings.IniFormat) + settings.setValue('editor/font_size', self.font_size_lineedit.text()) + settings.setValue('console/font_size', self.console_font_size_lineedit.text()) + if self.dark_theme_radio.isChecked(): + settings.setValue('theme', 'dark') + elif self.light_theme_radio.isChecked(): + settings.setValue('theme', 'light') + self.close() + + def loadSettings(self): + # Load saved settings + settings = QSettings('settings/app_settings.ini', QSettings.IniFormat) + font_size = settings.value('editor/font_size') + console_font_size = settings.value('console/font_size') + theme = settings.value('theme') + + + if font_size is not None: + self.font_size_lineedit.setText(font_size) + if console_font_size is not None: + self.console_font_size_lineedit.setText(console_font_size) + if theme == 'dark': + self.dark_theme_radio.setChecked(True) + elif theme == 'light': + self.light_theme_radio.setChecked(True) + + + + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = SettingsWindow() + sys.exit(app.exec_()) diff --git a/src/window/button/svg_button.py b/src/window/button/svg_button.py new file mode 100644 index 0000000..2172265 --- /dev/null +++ b/src/window/button/svg_button.py @@ -0,0 +1,77 @@ +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtSvg import QSvgRenderer +import sys + + +class SvgButton(QPushButton): + def __init__(self, svg_path, parent=None): + super().__init__(parent) + + # Load SVG files using QSvgRenderer + self.svg_renderer = QSvgRenderer(svg_path) + + # Specify icon size + icon_size = self.svg_renderer.defaultSize() * 0.2 + + # Create QPixmap and render SVG + self.default_pixmap = self.render_svg(icon_size, 0.5) + self.hover_pixmap = self.render_svg(icon_size, 1) + + # Create QIcon and set pixmap + icon = QIcon(self.default_pixmap) + + # Set an icon on the button + self.setIcon(icon) + self.setIconSize(icon_size) + + # Set up a style sheet for buttons to eliminate borders. + self.setStyleSheet("border: none; background-color: transparent;") + + + def resize(self, s): + icon_size = self.svg_renderer.defaultSize() * s + + # Create QPixmap and render SVG + self.default_pixmap = self.render_svg(icon_size, 0.5) + self.hover_pixmap = self.render_svg(icon_size, 1) + + # Create QIcon and set pixmap + icon = QIcon(self.default_pixmap) + + # Set an icon on the button + self.setIcon(icon) + self.setIconSize(icon_size) # Set icon size + + def render_svg(self, size, opacity): + pixmap = QPixmap(size) + pixmap.fill(Qt.transparent) # Set background transparent + painter = QPainter(pixmap) + painter.setOpacity(opacity) + self.svg_renderer.render(painter) + del painter # Explicitly destroy QPainter objects + return pixmap + + def enterEvent(self, event): + # Processing when the mouse enters the icon + icon = QIcon(self.hover_pixmap) + self.setIcon(icon) + + def leaveEvent(self, event): + # Processing when the mouse leaves the icon + icon = QIcon(self.default_pixmap) + self.setIcon(icon) + + +if __name__ == '__main__': + app = QApplication([]) + svg_path = "window/button/open_file.svg" + + window = QMainWindow() + window.setGeometry(100, 100, 200, 200) + button = SvgButton(svg_path, window) + button.setGeometry(0, 0, 100, 100) + + window.show() + sys.exit(app.exec_()) diff --git a/src/window/draw_object.py b/src/window/draw_object.py new file mode 100644 index 0000000..58868ad --- /dev/null +++ b/src/window/draw_object.py @@ -0,0 +1,164 @@ +import sys +import math +import json +import colorsys +import numpy as np +import pyqtgraph.opengl as gl +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtPrintSupport import * + + + + +def grid_draw(widget): + ROUTE_PATH = sys.path[1] if 2 == len(sys.path) else '.' + CONFIG_PATH = ROUTE_PATH + '/settings/settings.json' + with open(CONFIG_PATH, 'r') as f: + settings = json.load(f) + bed_size_x = settings['Hardware']['bed_size']['bed_size_x'] + bed_size_y = settings['Hardware']['bed_size']['bed_size_y'] + gz = gl.GLGridItem() + gz.setSize(bed_size_x, bed_size_y) + gz.setSpacing(10,10) + axis = gl.GLAxisItem() + axis.setSize(50,50,50) + x_text = gl.GLTextItem() + x_text.setData(pos=(50,0,0),text = 'x') + y_text = gl.GLTextItem() + y_text.setData(pos=(0,50,0),text = 'y') + z_text = gl.GLTextItem() + z_text.setData(pos=(0,0,50),text = 'z') + widget.addItem(axis) + widget.addItem(x_text) + widget.addItem(y_text) + widget.addItem(z_text) + widget.addItem(gz) + +def vecA_to_vecB(a, b): + """Calculation of direction and angle of nozzle drawing direction + + Args: + a (tuple): original normal vector + b (tuple): normal vector + + Returns: + tuple: direction and angle of nozzle drawing direction + """ + cross = np.zeros(3) + cross[0] = a[1]*b[2] - a[2]*b[1] + cross[1] = a[2]*b[0] - a[0]*b[2] + cross[2] = a[0]*b[1] - a[1]*b[0] + norm = math.sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]) + if norm == 0.0: + cross[0] = 0.0 + cross[1] = 0.0 + cross[2] = 1.0 + ang = 0.0 + else: + cross[0] /= norm + cross[1] /= norm + cross[2] /= norm + dot = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + ang = math.acos(dot) + return (cross, ang) + + + +def draw_full_object(widget, full_object): + """Create and draw a sequence of coordinates that the nozzle moves through the entire list of full_objects + + Args: + widget (GLViewWidget): widget of graphics view + full_object (list): list of path objects + """ + global pos_array,colors + pos_array = [] + colors = [] + for idx, path in enumerate(full_object): + coord = path.coords + norms = path.norms + color = np.zeros((len(coord), 4)) + for i in range(len(coord)): + z = coord[i][2] + hue = z % 360 + rgb = colorsys.hsv_to_rgb(hue/360, 1, 1) + color[i] = (*rgb, 1) + coord = np.insert(coord, 1, coord[0], axis=0) + coord = np.append(coord, [coord[-1]], axis=0) + + color = np.insert(color, 0, (1, 1, 1, 0.1), axis=0) + color = np.append(color, [(1, 1, 1, 0.1)], axis=0) + + pos_array.append(coord) + colors.append(color) + pos_array = np.concatenate(pos_array) + colors = np.concatenate(colors) + + plt = gl.GLLinePlotItem(pos=pos_array, color=colors, width=0.5, antialias=True) + + widget.addItem(plt) + slider_layer = len(full_object)-1 + slider_segment = len(full_object[-1].coords) + current_path = full_object[slider_layer] + current_path_plot_coords = current_path.coords[:slider_segment] + plt_last = gl.GLLinePlotItem(pos= current_path_plot_coords, color = 'w', width = 1, antialias = True) + widget.addItem(plt_last) + norm = current_path.norms[slider_segment - 1] + + if slider_layer > 0 and slider_segment > 0: + mesh = gl.MeshData.cylinder(rows=8, cols=8, radius=[1.0, 5.0], length=10.0) + plt = gl.GLMeshItem(meshdata=mesh, smooth=True, color=(1.0, 1.0, 1.0, 0.5), shader='shaded') + plt.setGLOptions('translucent') + pos_x = current_path_plot_coords[-1][0] + pos_y = current_path_plot_coords[-1][1] + pos_z = current_path_plot_coords[-1][2] + (cross, ang) = vecA_to_vecB((0, 0, 1), norm) + plt.rotate(math.degrees(ang), cross[0], cross[1], cross[2]) + plt.translate(pos_x, pos_y, pos_z) + widget.addItem(plt) + + + +def draw_object_slider(widget, full_object, slider_layer, slider_segment): + """Calculates how many segments to draw from the slider value and sets subsequent segments as transparent + + Args: + widget (GLViewWidget): widget of graphics view + full_object (list): list of path objects + slider_layer (int): slider_layer value + slider_segment (int): segment_layer value + """ + global pos_array, colors + colors_copy = np.copy(colors) + segment_each_path = 0 + for idx, path in enumerate(full_object): + + if idx < slider_layer : + segment_each_path += len((full_object[idx].coords)) + segment_each_path += 2 + segment_each_path += slider_segment + colors_copy[segment_each_path:, 3] = 0.0 + plt = gl.GLLinePlotItem(pos=pos_array, color=colors_copy, width=0.5, antialias=True) + + widget.addItem(plt) + current_path = full_object[slider_layer] + current_path_plot_coords = current_path.coords[:slider_segment] + plt_last = gl.GLLinePlotItem(pos= current_path_plot_coords, color = 'w', width = 1, antialias = True) + widget.addItem(plt_last) + norm = current_path.norms[slider_segment - 1] + + if slider_layer > 0 and slider_segment > 0: + mesh = gl.MeshData.cylinder(rows=8, cols=8, radius=[1.0, 5.0], length=10.0) + plt = gl.GLMeshItem(meshdata=mesh, smooth=True, color=(1.0, 1.0, 1.0, 0.5), shader='shaded') + plt.setGLOptions('translucent') + pos_x = current_path_plot_coords[-1][0] + pos_y = current_path_plot_coords[-1][1] + pos_z = current_path_plot_coords[-1][2] + (cross, ang) = vecA_to_vecB((0, 0, 1), norm) + plt.rotate(math.degrees(ang), cross[0], cross[1], cross[2]) + plt.translate(pos_x, pos_y, pos_z) + widget.addItem(plt) + + diff --git a/src/window/editor/completer.py b/src/window/editor/completer.py new file mode 100644 index 0000000..ac67e71 --- /dev/null +++ b/src/window/editor/completer.py @@ -0,0 +1,139 @@ +import inspect +import re +import numpy as np +from PyQt5.QtWidgets import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +import ast +import gcoordinator as gc + + +def extract_variable_names(code_str, names): + + try: # try/except for when broken grammar code is sent + tree = ast.parse(code_str) + for node in ast.walk(tree): + if isinstance(node, ast.Name): + names.add(node.id) + except: + pass + return names + + +def extract_function_names(code_string, function_names): + try: # try/except for when broken grammar code is sent + def visit_FunctionDef(node): + function_names.add(node.name) + tree = ast.parse(code_string) + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + visit_FunctionDef(node) + except: + pass + function_names.update(['np.'+method for method in dir(np) if not method.startswith('__')]) + return function_names + +def extract_class_names(code_str, names): + + try: # try/except for when broken grammar code is sent + tree = ast.parse(code_str) + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef): + names.add(node.id) + except: + pass + return names + +class Completer(QCompleter): + def __init__(self, parent=None): + super().__init__( parent) + self.word_list = [] + self.variable_set = set([]) + self.function_set = set([]) + self.class_set = set([]) + self.setCompletionMode(QCompleter.PopupCompletion) + self.setCaseSensitivity(Qt.CaseInsensitive) + self.setModel(QStringListModel(self.word_list)) + self.activated.connect(self.insertCompletion) + + def set_words(self): + self.completion_model = QStringListModel(self.word_list) + self.setModel(self.completion_model) + self.setCompletionMode(QCompleter.PopupCompletion) + self.setCaseSensitivity(Qt.CaseInsensitive) + + def insertCompletion(self, completion): + if self.widget() != self.parent(): + return + tc = self.parent().textCursor() + extra = len(completion) - len(self.completionPrefix()) + print(f'{extra=}') + + if completion in self.function_set: + tc.movePosition(QTextCursor.Left) + tc.movePosition(QTextCursor.EndOfWord) + if extra == 0: + tc.insertText("()") + else: + tc.insertText(completion[-extra:]+"()") + tc.movePosition(QTextCursor.PreviousCharacter) + self.parent().setTextCursor(tc) + elif completion in self.variable_set: + tc.movePosition(QTextCursor.Left) + tc.movePosition(QTextCursor.EndOfWord) + tc.insertText(completion[-extra:]) + self.parent().setTextCursor(tc) + tc.movePosition(QTextCursor.Right) + else: + tc.insertText(completion[-extra:]) + + def update_word_list(self): + # Initialize the list so that unused variable names, etc. + # do not remain in word_list + self.word_list = [] + self.word_list_path_generator(gc.path_generator) + text = self.parent().toPlainText() + + self.variable_set = extract_variable_names(text, self.variable_set) + self.function_set = extract_function_names(text, self.function_set) + + cursor_position = self.parent().textCursor().position() + text_before_cursor = self.parent().toPlainText()[:cursor_position] + words_before_cursor = text_before_cursor.split() + completionPrefix = words_before_cursor[-1] if words_before_cursor else "" # 途中までに入植した文字列 + + try: # Exclude pre-written strings from the set of names + self.variable_set.remove(completionPrefix) + self.function_set.remove(completionPrefix) + except: # Processing when prefix is not in variables + pass + self.word_list += self.variable_set + self.word_list += self.function_set + + self.word_list = sorted(set(self.word_list)) + + self.set_words() + + def word_list_path_generator(self, module): + classes = [] + for name, obj in inspect.getmembers(module): + if inspect.isclass(obj): + classes.append(name) + # Create a word list for autocomplete + + for class_name in classes: + # Add class name + self.word_list.append(class_name) + # Get and add methods in the class + for method_name in dir(getattr(module, class_name)): + if not method_name.startswith('_'): + self.word_list.append(f"{class_name}.{method_name}") + + def word_list_print_setting(self, module): + self.word_list.append('print_settings') + for name, obj in inspect.getmembers(module): + if not name.startswith('_') and not inspect.ismodule(obj): + self.word_list.append(f'print_settings.{name}') + + diff --git a/src/window/editor/line_number.py b/src/window/editor/line_number.py new file mode 100644 index 0000000..9ec6fc1 --- /dev/null +++ b/src/window/editor/line_number.py @@ -0,0 +1,84 @@ +from PyQt5.QtWidgets import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +class LineNumberWidget(QTextBrowser): + def __init__(self, widget: QTextEdit): + super().__init__() + self.widget = widget + self.lineCount = widget.document().blockCount() + self.fontSize = int(widget.font().pointSizeF()) + self.styleInit() + self.resize(40, widget.height()) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setTextInteractionFlags(Qt.NoTextInteraction) + self.verticalScrollBar().setEnabled(False) + self.verticalScrollBar().setValue(0) + + self.widget.verticalScrollBar().valueChanged.connect(self.__changeLineWidgetScrollAsTargetedWidgetScrollChanged) + self.initLineCount() + + def __changeLineWidgetScrollAsTargetedWidgetScrollChanged(self, v): + self.verticalScrollBar().setValue(v) + + def eventFilter(self, obj, event): + if event.type() == QEvent.Resize: + self.resize(40, obj.height()) + return True + return False + + def initLineCount(self): + for n in range(1, self.lineCount+1): + self.append(str(n)) + def changeLineCount(self, n): + max_one = max(self.lineCount, n) + diff = n - self.lineCount + if max_one == self.lineCount: + first_v = self.verticalScrollBar().value() + for i in range(self.lineCount, self.lineCount + diff, -1): + self.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor) + self.moveCursor(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) + self.moveCursor(QTextCursor.End, QTextCursor.KeepAnchor) + self.textCursor().removeSelectedText() + self.textCursor().deletePreviousChar() + last_v = self.verticalScrollBar().value() + if abs(first_v-last_v) != 2: + self.verticalScrollBar().setValue(first_v) + else: + for i in range(self.lineCount, self.lineCount + diff): + self.append(str(i + 1)) + + self.lineCount = n + self.styleInit() + self.verticalScrollBar().setValue(0) + + + def setValue(self, v): + self.verticalScrollBar().setValue(v) + + def setFontSize(self, s: float): + self.fontSize = int(s) + self.styleInit() + + def styleInit(self): + style = ''' + QTextBrowser { + background: transparent; + border: none; + color: #AAA; + font: ''' + str(self.fontSize) + '''pt; + text-align: right; + } + ''' + self.setStyleSheet(style) + '''if self.lineCount<99: + self.setFixedWidth(self.fontSize * 2) + else:''' + self.setFixedWidth(self.fontSize * 3) + + + def updateLineCount(self): + new_line_count = self.widget.document().blockCount() + if new_line_count != self.lineCount: + self.changeLineCount(new_line_count) \ No newline at end of file diff --git a/src/syntax_pars.py b/src/window/editor/syntax_pars.py similarity index 87% rename from src/syntax_pars.py rename to src/window/editor/syntax_pars.py index 6f57d81..a7bbbf2 100644 --- a/src/syntax_pars.py +++ b/src/window/editor/syntax_pars.py @@ -27,8 +27,10 @@ def format(color, style=''): STYLES = { 'library_word': format([ 71,176,155]), - 'keyword': format([86,155,212]), + 'keyword': format([86, 156, 214]), 'other_keyword': format([ 185,127,181]), + 'variables': format([86, 156, 214]), + #'method': format([ 240,230,140]), 'operator': format([ 199,199,199]), 'brace': format([233,197,3]), 'defclass': format([205,205,159], 'bold'), @@ -45,21 +47,27 @@ class PythonHighlighter(QSyntaxHighlighter): """ # Python keywords library_words = [ - 'np','math' + 'np','math', 'Transform', 'print_settings', 'Path' ] keywords = [ 'and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'except', 'exec', 'finally', - 'from', 'global', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', + 'from', 'global', 'in', + 'is', 'lambda', 'not', 'or', 'pass', 'raise', 'try', 'while', 'yield', 'None', 'True', 'False', ] other_keywords = [ 'if','else','elif','return','for','import' ] + variables = [ + 'full_object' + ] + '''method = [ + 'append' + ]''' # Python operators operators = [ @@ -89,7 +97,13 @@ def __init__(self, document): self.tri_double = (QRegExp('"""'), 2, STYLES['string2']) rules = [] + method_pattern = r'\b(\w+)\(' + method_format = format([ 220, 220, 170]) + rules.append((method_pattern, 1, method_format)) + '''constant_pattern = r'\b[A-Z]+\b' + constant_format = format([ 75, 162, 235]) + rules.append((constant_pattern, 1, constant_format))''' # Keyword, operator, and brace rules rules += [(r'\b%s\b' % w, 0, STYLES['library_word']) for w in PythonHighlighter.library_words] @@ -97,6 +111,10 @@ def __init__(self, document): for w in PythonHighlighter.keywords] rules += [(r'\b%s\b' % w, 0, STYLES['other_keyword']) for w in PythonHighlighter.other_keywords] + rules += [(r'\b%s\b' % w, 0, STYLES['variables']) + for w in PythonHighlighter.variables] + '''rules += [(r'\b(\w+)\(' % w, 0, STYLES['method']) + for w in PythonHighlighter.method]''' rules += [(r'%s' % o, 0, STYLES['operator']) for o in PythonHighlighter.operators] rules += [(r'%s' % b, 0, STYLES['brace']) @@ -125,6 +143,7 @@ def __init__(self, document): (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']), ] + # Build a QRegExp for each pattern self.rules = [(QRegExp(pat), index, fmt) diff --git a/src/window/editor/text_editor.py b/src/window/editor/text_editor.py new file mode 100644 index 0000000..f58760d --- /dev/null +++ b/src/window/editor/text_editor.py @@ -0,0 +1,169 @@ +from PyQt5.QtWidgets import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtPrintSupport import * +from window.editor.completer import Completer + + + + +class TextEditor(QTextEdit): + def __init__(self, parent=None): + super().__init__(parent) + self.completer = Completer(self) + self.completer.setWidget(self) + self.trigger = '' + popup = self.completer.popup() + + popup.setStyleSheet(""" + background-color: rgb(32, 33, 36); + color: white; + selection-background-color: rgb(142, 177, 250); /* 選択されているアイテムの背景色 */ + selection-color: black; /* 選択されているアイテムの文字色 */ + """) + + self.textChanged.connect(self.completer.update_word_list) + + + def print_change(self): + print('Changed!!') + def repaint_editor(self): + print('repaint') + self.update() + + + + def indent(self): + if not self.textCursor().hasSelection(): + self.insertPlainText(" " * 4) + # If there is a selection, insert an indent at the beginning + # of each line within the selection + else: + # Get the start and end line numbers of the selected range + start = self.textCursor().selectionStart() + end = self.textCursor().selectionEnd() + start_block = self.document().findBlock(start).blockNumber() + end_block = self.document().findBlock(end).blockNumber() + # Process each row of the selection + for block_number in range(start_block, end_block + 1): + block = self.document().findBlockByNumber(block_number) + cursor = self.textCursor() + cursor.setPosition(block.position()) + cursor.movePosition(QTextCursor.StartOfLine) + cursor.insertText(" " * 4) + + def unindent(self): + start = self.textCursor().selectionStart() + end = self.textCursor().selectionEnd() + start_block = self.document().findBlock(start).blockNumber() + end_block = self.document().findBlock(end).blockNumber() + + # Process each row of the selection + for block_number in range(start_block, end_block + 1): + block = self.document().findBlockByNumber(block_number) + cursor = self.textCursor() + cursor.setPosition(block.position()) + cursor.movePosition(QTextCursor.StartOfLine) + + # Unindent spaces + line_text = block.text() + if len(line_text) > 4 and line_text.startswith(" " * 4): + cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 4) + cursor.removeSelectedText() + elif line_text.startswith(" "): + cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 1) + cursor.removeSelectedText() + + + def textUnderCursor(self): + tc = self.textCursor() + tc.select(QTextCursor.WordUnderCursor) + return tc.selectedText() + + + + def pastePlainText(self): + clipboard = QApplication.clipboard() + plain_text = clipboard.text() + print(plain_text) + self.insertPlainText(plain_text) + + def keyPressEvent(self, event): + # auto_complete + if self.completer.popup().isVisible(): + if event.key() in (Qt.Key_Enter, Qt.Key_Return): + self.completer.popup().hide() # Hide the completion widget when the Enter key is pressed + event.ignore() + return + if event.key() in (Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab): + self.completer.popup().hide() + event.ignore() + return + + #Auto indentation process + if event.key() in (Qt.Key_Return, Qt.Key_Enter): + indent_width = 4 + line_number = self.textCursor().blockNumber() + line_text = self.document().findBlockByLineNumber(line_number).text() + indent_level = line_text.count(" " * indent_width) + if line_text.endswith(":"): + indent_level += 1 + self.insertPlainText("\n") + self.insertPlainText( " " * indent_width * indent_level) + return + + if event.key() == Qt.Key_Tab: + self.indent() + return + + if event.key() == Qt.Key_Backtab: + if event.modifiers() == Qt.ShiftModifier: # Check if Shift is pressed at the same time + self.unindent() + return + + + if event.key() in [Qt.Key_Delete, Qt.Key_Backspace]: + cursor = self.textCursor() + position = cursor.position() + + # Get the 4 characters before the cursor + cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 4) + text = cursor.selectedText() + + if text == ' ' * 4: + # Fix cursor position + new_position = position - 4 + cursor.setPosition(new_position) + + # Delete 4 spaces at once + cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 4) + cursor.removeSelectedText() + + self.setTextCursor(cursor) + return + if event.matches(QKeySequence.Paste): + print('pasta plain') + self.pastePlainText() + else: + super(TextEditor, self).keyPressEvent(event) + + + # auto complete + cursor_position = self.textCursor().position() + completionPrefix = self.toPlainText()[:cursor_position].split()[-1] + if completionPrefix.startswith(self.trigger): + completionPrefix = completionPrefix[len(self.trigger):] + else: + self.completer.popup().hide() + return + if len(completionPrefix) < 1: + self.completer.popup().hide() + return + if completionPrefix != self.completer.completionPrefix(): + self.completer.setCompletionPrefix(completionPrefix) + self.completer.popup().setCurrentIndex(self.completer.completionModel().index(0, 0)) + + cr = self.cursorRect() + cr.setWidth(self.completer.popup().sizeHintForColumn(0) + self.completer.popup().verticalScrollBar().sizeHint().width()) + self.completer.complete(cr) diff --git a/src/window/gcode_export_window.py b/src/window/gcode_export_window.py new file mode 100644 index 0000000..2bd5c4d --- /dev/null +++ b/src/window/gcode_export_window.py @@ -0,0 +1,57 @@ + +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtPrintSupport import * + + +class GcodeExportWindow(QWidget): + def __init__(self): + super().__init__() + self.title = 'Save G-code file' + self.left = 10 + self.top = 10 + self.width = 500 + self.height = 400 + self.initUI() + + def initUI(self): + self.setWindowTitle(self.title) + self.setGeometry(self.left, self.top, self.width, self.height) + layout = QVBoxLayout() + self.button = QPushButton('Save G-code file', self) + self.button.setToolTip('Click to save G-code file') + self.button.clicked.connect(self.saveFileDialog) + + self.textEdit = QTextEdit(self) + self.textEdit.setFont(QFont("Courier New", 10)) + self.showGCodePreview() + + self.label = QLabel(self) + self.label.setFont(QFont("Arial", 10, QFont.Bold)) + self.label.setText("Showing the first 1000 lines of G-code file.") + + layout.addWidget(self.button) + layout.addWidget(self.label) + layout.addWidget(self.textEdit) + self.setLayout(layout) + self.show() + + def showGCodePreview(self): + with open("buffer/G-coordinator.gcode", 'r') as f: + content = f.readlines() + if len(content) > 1000: + content = content[:1000] + content = ''.join(content) + self.textEdit.setPlainText(content) + + def saveFileDialog(self): + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + fileName, _ = QFileDialog.getSaveFileName(self,"Save G-code file","","G-code Files (*.gcode);;All Files (*)", options=options) + if fileName: + with open("buffer/G-coordinator.gcode", 'r') as f: + content = f.read() + with open(fileName, 'w') as new_f: + new_f.write(content) + self.showGCodePreview() \ No newline at end of file diff --git a/src/window/machine_settings_window.py b/src/window/machine_settings_window.py new file mode 100644 index 0000000..0fd7dcc --- /dev/null +++ b/src/window/machine_settings_window.py @@ -0,0 +1,147 @@ +import sys +import json +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtPrintSupport import * +from pyqtgraph.parametertree import Parameter, ParameterTree +import configparser + +class MachineSettingsDialog(QWidget): + """ + Machine Settings Window + """ + def __init__(self): + super().__init__() + self.setWindowTitle("Machine Settings") + self.read_setting() + + # Parameters + self.params = [ + {'name': 'Hardware', 'type': 'group', 'children': [ + {'name' : 'kinematics', 'type': 'list', 'values': ['Cartesian', 'NozzleTilt', 'BedTilt', 'BedRotate'], 'value':str(self.settings['Hardware']['kinematics'])}, + {'name' : 'bed_size', 'type': 'group', 'children': [ + {'name': 'bed_size_x', 'type': 'int', 'value': float(self.settings['Hardware']['bed_size']['bed_size_x'])}, + {'name': 'bed_size_y', 'type': 'int', 'value': float(self.settings['Hardware']['bed_size']['bed_size_y'])}, + {'name': 'bed_size_z', 'type': 'int', 'value': float(self.settings['Hardware']['bed_size']['bed_size_z'])} + ]} + ]}, + {'name': 'Kinematics', 'type': 'group', 'children': [ + {'name' : 'NozzleTilt', 'type': 'group', 'children':[ + {'name': 'tilt_code', 'type': 'str', 'value': str(self.settings ['Kinematics']['NozzleTilt']['tilt_code'])}, + {'name': 'rot_code', 'type': 'str', 'value': str(self.settings ['Kinematics']['NozzleTilt']['rot_code'])}, + {'name': 'tilt_offset', 'type': 'float', 'value': float(self.settings['Kinematics']['NozzleTilt']['tilt_offset'])}, + {'name': 'rot_offset', 'type': 'float', 'value': float(self.settings['Kinematics']['NozzleTilt']['rot_offset'])} + ]}, + {'name' : 'BedTiltBC', 'type': 'group', 'children':[ + {'name': 'tilt_code', 'type': 'str', 'value': str(self.settings ['Kinematics']['BedTiltBC']['tilt_code'])}, + {'name': 'rot_code', 'type': 'str', 'value': str(self.settings ['Kinematics']['BedTiltBC']['rot_code'])}, + {'name': 'tilt_offset', 'type': 'float', 'value': float(self.settings['Kinematics']['BedTiltBC']['tilt_offset'])}, + {'name': 'rot_offset', 'type': 'float', 'value': float(self.settings['Kinematics']['BedTiltBC']['rot_offset'])}, + {'name': 'div_distance', 'type': 'float', 'value': float(self.settings['Kinematics']['BedTiltBC']['div_distance'])} + ]}, + {'name' : 'BedRotate', 'type': 'group', 'children':[ + {'name': 'rot_code', 'type': 'str', 'value': str(self.settings ['Kinematics']['BedRotate']['rot_code'])}, + {'name': 'rot_offset', 'type': 'float', 'value': float(self.settings['Kinematics']['BedRotate']['rot_offset'])}, + {'name': 'div_distance', 'type': 'float', 'value': float(self.settings['Kinematics']['BedRotate']['div_distance'])}, + + ]}, + + ], 'expanded': False}, + ] + + + self.init_ui() + + def init_ui(self): + """ + initialize ui + """ + layout = QVBoxLayout() + # Parameter tree + self.parameter_tree = ParameterTree() + self.p = Parameter.create(name='params', type='group', children=self.params) + self.parameter_tree.setParameters(self.p) + self.p.sigTreeStateChanged.connect(self.change) + layout.addWidget(self.parameter_tree) + + # Start Gcode Editor and gcode + start_label = QLabel('Start Gcode Editor', self) + layout.addWidget(start_label) + self.start_edit = QPlainTextEdit(self) + layout.addWidget(self.start_edit) + + start_file_path = 'settings/start_gcode.txt' + with open(start_file_path, 'r') as file: + content = file.read() + self.start_edit.setPlainText(content) + + # End Gcode Editor + end_label = QLabel('End Gcode Editor', self) + layout.addWidget(end_label) + self.end_edit = QPlainTextEdit(self) + layout.addWidget(self.end_edit) + + end_file_path = 'settings/end_gcode.txt' + with open(end_file_path, 'r') as file: + content = file.read() + self.end_edit.setPlainText(content) + + + + # Save button + save_button = QPushButton("Save") + save_button.clicked.connect(self.save_settings) + layout.addWidget(save_button) + self.setLayout(layout) + + def read_setting(self): + ROUTE_PATH = sys.path[1] if 2 == len(sys.path) else '.' + CONFIG_PATH = ROUTE_PATH + '/settings/settings.json' + with open(CONFIG_PATH, 'r') as f: + self.settings = json.load(f) + + def change(self, param, changes): + print("machine tree changes:") + + for param, change, data in changes: + path = self.p.childPath(param) + # Set section and key based on path length + if len(path) == 3: + section, subsection, key = path[0], path[1], path[2] + self.settings[section][subsection][key] = data + print(section, subsection, key, ":", data) + elif len(path) == 2: + section, key = path[0], path[1] + self.settings[section][key] = data + print(section, key, ":", data) + else: + continue + print('-------------') + + # Write updated settings to json file + ROUTE_PATH = sys.path[1] if 2 == len(sys.path) else '.' + CONFIG_PATH = ROUTE_PATH + '/settings/settings.json' + with open(CONFIG_PATH, 'w') as f: + json.dump(self.settings, f, indent=4) + + + + def save_settings(self): + + start_file_path = 'settings/start_gcode.txt' + content = self.start_edit.toPlainText() + with open(start_file_path, 'w') as file: + file.write(content) + + + end_file_path = 'settings/end_gcode.txt' + content = self.end_edit.toPlainText() + with open(end_file_path, 'w') as file: + file.write(content) + + + # Show restart notification + QMessageBox.information(self, "Restart Required", "Changes will take effect after restarting the application.") + + self.close() \ No newline at end of file diff --git a/src/window/main/file_operations.py b/src/window/main/file_operations.py new file mode 100644 index 0000000..3ac2b5f --- /dev/null +++ b/src/window/main/file_operations.py @@ -0,0 +1,74 @@ +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +class FileOperation: + def open(self, main_window): + # getting path and bool value + path, _ = QFileDialog.getOpenFileName(main_window, "Open file", "", + "Text documents (*.txt);All files (*.*)") + # if path is true + if path: + # try opening path + try: + with open(path, 'r') as f: + # read the file + text = f.read() + # if some error occurred + except Exception as e: + # show error using critical method + self.dialog_critical(str(e)) + # else + else: + # update path value + main_window.path = path + # update the text + main_window.editor.setPlainText(text) + # update the title + main_window.update_title() + + def save(self, main_window): + # if there is no save path + if main_window.path is None: + # call save as method + return main_window.file_save_as() + # else call save to path method + #main_window._save_to_path(main_window.path) + self._save_to_path(main_window, main_window.path) + # action called by save as action + + def save_as(self, main_window): + # opening path + path, _ = QFileDialog.getSaveFileName(main_window, "Save file", "", + "Text documents (*.txt);All files (*.*)") + # if dialog is cancelled i.e no path is selected + if not path: + # return this method + # i.e no action performed + return + + # else call save to path method + #main_window._save_to_path(path) + self._save_to_path(main_window, path) + + def _save_to_path(self, main_window, path): + # get the text + text = main_window.editor.toPlainText() + # try catch block + try: + # opening file to write + with open(path, 'w') as f: + # write text in the file + f.write(text) + + # if error occurs + except Exception as e: + # show error using critical + main_window.dialog_critical(str(e)) + + # else do this + else: + # change path + main_window.path = path + # update the title + main_window.update_title() \ No newline at end of file diff --git a/src/window/main/menu_bar.py b/src/window/main/menu_bar.py new file mode 100644 index 0000000..32b956a --- /dev/null +++ b/src/window/main/menu_bar.py @@ -0,0 +1,82 @@ +from PyQt5.QtWidgets import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +#import markdown2 + +class MenuBar: + def settings(self, main_window): + menubar = main_window.menuBar() + + menu_data = { + 'File': { + 'New': {'shortcut': 'Ctrl+N', 'triggered': main_window.new}, + 'Open': {'shortcut': 'Ctrl+O', 'triggered': main_window.file_open}, + 'Save': {'shortcut': 'Ctrl+S', 'triggered': main_window.file_save}, + 'Save As': {'shortcut': 'Ctrl+Shift+S', 'triggered': main_window.file_save_as} + }, + 'Settings': { + 'Settings': {'shortcut': 'Ctrl+,', 'triggered': main_window.settings} + }, + 'Edit': { + 'Cut': {'shortcut': 'Ctrl+X'}, + 'Copy': {'shortcut': 'Ctrl+C'}, + 'Paste': {'shortcut': 'Ctrl+V'}, + 'Undo': {'shortcut': 'Ctrl+Z'}, + 'Redo': {'shortcut': 'Ctrl+Y'} + }, + 'Run': { + 'Run': {'shortcut': 'Ctrl+R', 'triggered': main_window.run} + }, + 'Help': { + 'Documentation': {'triggered': main_window.documentation}, + 'Version Information': {'triggered': main_window.version_info}, + 'Contact Us': {'triggered': main_window.contact_us} + } + } + + for menu_name, actions in menu_data.items(): + menu = menubar.addMenu(menu_name) + for action_name, action_data in actions.items(): + action = QAction(action_name, main_window) + shortcut = action_data.get('shortcut', None) + triggered = action_data.get('triggered', None) + if shortcut: + action.setShortcut(shortcut) + if triggered: + action.triggered.connect(triggered) + menu.addAction(action) + + def documentation(self,main_window): + # The document menu was once hidden due to problems with the README display, + # such as img not being displayed, code highlighting not being done, etc. + with open('../README.md', 'r') as file: + readme_text = file.read() + + dialog = ReadmeDialog(main_window) + dialog.set_readme_text(readme_text) + dialog.exec_() + + + def version_info(self, main_window): + version = 'G-coordinator 3.0.0' + QMessageBox.information(main_window, 'Version Information', f'Version: {version}') + + def contact_us(self, main_window): + contact = 'gcoordinator.3dp@gmail.com' + QMessageBox.information(main_window, 'Contact', f'Contact: {contact}\n Twitter: @Gcoordinator3DP\n Author: @tamutamu3D') + +class ReadmeDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("README") + + layout = QVBoxLayout() + self.text_edit = QTextEdit(self) + layout.addWidget(self.text_edit) + self.setLayout(layout) + + def set_readme_text(self, readme_text): + pass + # Dialog to display readme (not yet completed) + #html = markdown2.markdown(readme_text) + #self.text_edit.setHtml(html) \ No newline at end of file diff --git a/src/window/main/parameter_tree.py b/src/window/main/parameter_tree.py new file mode 100644 index 0000000..2ff4ba6 --- /dev/null +++ b/src/window/main/parameter_tree.py @@ -0,0 +1,81 @@ +import sys +import json +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from pyqtgraph.parametertree import ParameterTree as pgParameterTree +from pyqtgraph.parametertree import Parameter + +class ParameterTree(pgParameterTree): + def __init__(self): + super(ParameterTree, self).__init__() + + def read_setting(self): + ROUTE_PATH = sys.path[1] if 2 == len(sys.path) else '.' + CONFIG_PATH = ROUTE_PATH + '/settings/settings.json' + with open(CONFIG_PATH, 'r') as f: + self.settings = json.load(f) + + def parameter_tree_setting(self): + self.params = [ + {'name': 'nozzle', 'type': 'group', 'children': [ + {'name': 'nozzle_diameter', 'type': 'float', 'value': float(self.settings['Print']['nozzle']['nozzle_diameter'])}, + {'name': 'filament_diameter', 'type': 'float', 'value': float(self.settings['Print']['nozzle']['filament_diameter'])}, + ]}, + {'name': 'layer', 'type': 'group', 'children': [ + {'name': 'layer_height', 'type': 'float', 'value': float(self.settings['Print']['layer']['layer_height'])}, + ]}, + + {'name': 'speed', 'type': 'group', 'children': [ + {'name': 'print_speed', 'type': 'int', 'value': int(self.settings['Print']['speed']['print_speed'])}, + {'name': 'travel_speed', 'type': 'int', 'value': int(self.settings['Print']['speed']['travel_speed'])}, + ]}, + {'name': 'origin', 'type': 'group', 'children': [ + {'name': 'x', 'type': 'int', 'value': int(self.settings['Print']['origin']['x'])}, + {'name': 'y', 'type': 'int', 'value': int(self.settings['Print']['origin']['y'])}, + ]}, + {'name': 'fan_speed', 'type': 'group', 'children': [ + {'name': 'fan_speed', 'type': 'int', 'value': int(self.settings['Print']['fan_speed']['fan_speed'])}, + ]}, + {'name': 'temperature', 'type': 'group', 'children': [ + {'name': 'nozzle_temperature', 'type': 'int', 'value': int(self.settings['Print']['temperature']['nozzle_temperature'])}, + {'name': 'bed_temperature', 'type': 'int', 'value': int(self.settings['Print']['temperature']['bed_temperature'])}, + ]}, + {'name': 'travel_option', 'type': 'group', 'children': [ + {'name': 'retraction', 'type': 'bool', 'value': bool(self.settings['Print']['travel_option']['retraction'])}, + {'name': 'retraction_distance', 'type': 'float', 'value': float(self.settings['Print']['travel_option']['retraction_distance'])}, + {'name': 'unretraction_distance', 'type': 'float', 'value': float(self.settings['Print']['travel_option']['unretraction_distance'])}, + {'name': 'z_hop', 'type': 'bool', 'value': bool(self.settings['Print']['travel_option']['z_hop'])}, + {'name': 'z_hop_distance', 'type': 'float', 'value': float(self.settings['Print']['travel_option']['z_hop_distance'])}, + ]}, + {'name': 'extrusion_option', 'type': 'group', 'children': [ + {'name': 'extrusion_multiplier', 'type': 'float', 'value': float(self.settings['Print']['extrusion_option']['extrusion_multiplier'])}, + ]}, + + ] + + self.p = Parameter.create(name='params', type='group', children=self.params) + self.p.sigTreeStateChanged.connect(self.change) + + + + def change(self, param, changes): + """ + Update the settings and print the changes made to the console. + + Args: + param: The parameter that was changed. + changes: A list of tuples containing the parameter, change type, and new data. + + Returns: + None + """ + print("tree changes:") + for param, change, data in changes: + path = self.p.childPath(param) + print(path[0], path[1],":", str(data)) + self.settings['Print'][path[0]][path[1]] = data + with open('settings/settings.json', 'w') as f: + json.dump(self.settings, f, indent=4) + print('-------------') + diff --git a/src/window/main/ui_settings.py b/src/window/main/ui_settings.py new file mode 100644 index 0000000..519fa39 --- /dev/null +++ b/src/window/main/ui_settings.py @@ -0,0 +1,215 @@ +import sys +import platform +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtPrintSupport import * +from pyqtgraph import opengl +from window.editor.text_editor import TextEditor +from window.button.svg_button import SvgButton +from window.editor.line_number import LineNumberWidget +from window.editor.syntax_pars import PythonHighlighter +from window.main.parameter_tree import ParameterTree +from window.main.menu_bar import MenuBar + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.resize(1400, 800) + MainWindow.setObjectName("MainWindow") + self.left_pane_setting(MainWindow) + self.cetral_pane_setting(MainWindow) + self.right_pane_setting(MainWindow) + + self.menu_bar = MenuBar() + self.menu_bar.settings(self) + + self.splitter = QSplitter() + splitter_style_sheet = """ + QSplitter::handle { + background: gray; + } + QSplitter::handle:horizontal { + width: 1px; + } + QSplitter::handle:vertical { + height: 1px; + } + """ + self.splitter.setStyleSheet(splitter_style_sheet) + self.splitter.setOrientation(Qt.Horizontal) + self.splitter.addWidget(QWidget()) + self.splitter.addWidget(QWidget()) + self.splitter.addWidget(QWidget()) + self.splitter.widget(0).setLayout(self.left_pane_layout) + self.splitter.widget(1).setLayout(self.central_layout) + self.splitter.widget(2).setLayout(self.right_layout) + self.splitter.setSizes([300, 600, 220]) + + self.main_layout = QVBoxLayout() + self.main_layout.addWidget(self.splitter) + central_widget = QWidget(MainWindow) + central_widget.setLayout(self.main_layout) + MainWindow.setCentralWidget(central_widget) + MainWindow.setWindowTitle('Splitter with handle') + + self.open_button = SvgButton('resources/open_file.svg', MainWindow) + self.open_button.resize(0.12) + self.reload_button = SvgButton('resources/play.svg', MainWindow) + self.reload_button.resize(0.3) + if platform.system() == "Darwin": + self.open_button.setGeometry(18, 10, 60, 50) + self.reload_button.setGeometry(78, 10, 60, 50) + else: + self.open_button.setGeometry(18, 30, 60, 50) + self.reload_button.setGeometry(78, 30, 60, 50) + + + self.retranslateUi(MainWindow) + self.signal_connecter(MainWindow) + + + def left_pane_setting(self,MainWindow): + self.button_style_sheet = """ + QPushButton { + background-color: #CCCCCC; + color: #333333; + border: none; + padding: 10px 20px; + border-radius: 10px; + } + QPushButton:hover { + background-color: #AAAAAA; + } + """ + + + self.editor = TextEditor(MainWindow) + self.editor.setLineWrapMode(TextEditor.LineWrapMode.NoWrap) + self.editor.textChanged.connect(self.__line_widget_line_count_changed) + self.editor.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.editor.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + self.editor.setFont(QFont("Arial", 14)) + self.editor.setStyleSheet("""QTextEdit{ + color: #ccc; + background-color: #2b2b2b;}""") + self.highlight=PythonHighlighter(self.editor.document()) + self.line_number_widget = LineNumberWidget(self.editor) + + self.line_number_widget.setFontSize(14) + self.editor_layout = QtWidgets.QHBoxLayout() + self.editor_layout.addWidget(self.line_number_widget) + self.editor_layout.addWidget(self.editor) + self.editor_layout.setSpacing(0) + + self.message_console = QtWidgets.QTextEdit(MainWindow) + self.message_console.setMinimumHeight(30) + self.message_console.setReadOnly(True) + self.message_console.setStyleSheet("background-color: rgb(26, 26, 26);") + + self.left_pane_widget = QWidget(MainWindow) + self.message_splitter = QSplitter() + self.message_splitter.setOrientation(Qt.Vertical) + self.message_splitter.addWidget(QWidget()) + self.message_splitter.widget(0).setLayout(self.editor_layout) + self.message_splitter.addWidget(self.message_console) + self.message_splitter.setSizes([10,1]) + + self.left_pane_layout = QVBoxLayout() + spacer_widget = QtWidgets.QWidget() + spacer_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + spacer_widget.setFixedSize(100, 20) + self.left_pane_layout.addWidget(spacer_widget) + self.left_pane_layout.addWidget(self.message_splitter) + + def cetral_pane_setting(self, MainWindow): + self.graphicsView = opengl.GLViewWidget(MainWindow) + self.graphicsView.setBackgroundColor(QtGui.QColor(30, 30, 30)) + self.graphicsView.setCameraPosition(distance=120) + + self.segment_button_layout = QtWidgets.QHBoxLayout() + self.slider_segment = QtWidgets.QSlider(MainWindow) + self.slider_segment.setOrientation(QtCore.Qt.Horizontal) + self.left_button = QtWidgets.QToolButton(MainWindow) + self.right_button = QtWidgets.QToolButton(MainWindow) + self.segment_button_layout.addWidget(self.slider_segment) + self.segment_button_layout.addWidget(self.left_button) + self.segment_button_layout.addWidget(self.right_button) + + self.graphic_seg_layout = QtWidgets.QVBoxLayout() + self.graphic_seg_layout.addWidget(self.graphicsView) + self.graphic_seg_layout.addLayout(self.segment_button_layout) + + self.layer_button_layout = QtWidgets.QVBoxLayout() + self.slider_layer = QtWidgets.QSlider(MainWindow) + self.slider_layer.setOrientation(QtCore.Qt.Vertical) + self.slider_layout = QtWidgets.QHBoxLayout() + self.slider_layout.addWidget(self.slider_layer) + self.slider_layout.setContentsMargins(0, 0, 0, 0) + + self.up_button = QtWidgets.QToolButton(MainWindow) + self.down_button = QtWidgets.QToolButton(MainWindow) + self.layer_button_layout.addWidget(self.up_button) + self.layer_button_layout.addWidget(self.down_button) + + self.layer_layout = QtWidgets.QVBoxLayout() + self.layer_layout.addLayout(self.slider_layout) + self.layer_layout.addLayout(self.layer_button_layout) + + self.up_button.setArrowType(Qt.UpArrow) + self.down_button.setArrowType(Qt.DownArrow) + self.left_button.setArrowType(Qt.LeftArrow) + self.right_button.setArrowType(Qt.RightArrow) + + self.central_layout = QtWidgets.QHBoxLayout() + self.central_layout.addLayout(self.graphic_seg_layout) + self.central_layout.addLayout(self.layer_layout) + + def right_pane_setting(self, MainWindow): + self.machine_settings_button = QtWidgets.QPushButton(MainWindow) + self.parameter_tree = ParameterTree() + self.parameter_tree.read_setting() + self.parameter_tree.parameter_tree_setting() + self.parameter_tree.setParameters(self.parameter_tree.p, showTop=True) + self.parameter_tree.resize(250,540) + + self.gcode_export_button = QtWidgets.QPushButton(MainWindow) + self.gcode_export_button.setStyleSheet(self.button_style_sheet) + self.right_layout = QtWidgets.QVBoxLayout() + self.right_layout.addWidget(self.machine_settings_button) + self.right_layout.addWidget(self.parameter_tree) + self.right_layout.addWidget(self.gcode_export_button) + + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.left_button.setText(_translate("MainWindow", "...")) + self.right_button.setText(_translate("MainWindow", "...")) + self.up_button.setText(_translate("MainWindow", "...")) + self.down_button.setText(_translate("MainWindow", "...")) + self.gcode_export_button.setText(_translate("MainWindow", "Gcode Export")) + self.machine_settings_button.setText(_translate("MainWindow", "Machine settings")) + + def signal_connecter(self, MainWindow): + self.open_button.pressed.connect(MainWindow.file_open) + self.reload_button.pressed.connect(MainWindow.run) + self.machine_settings_button.pressed.connect(MainWindow.open_machine_settings_window) + + self.gcode_export_button.pressed.connect(MainWindow.render_execution_result) + self.gcode_export_button.pressed.connect(MainWindow.Gcode_create) + self.slider_layer.valueChanged['int'].connect(MainWindow.redraw_layer_object) + self.up_button.pressed.connect(MainWindow.up_button_pressed) + self.down_button.pressed.connect(MainWindow.down_button_pressed) + self.slider_segment.valueChanged['int'].connect(MainWindow.redraw_segment_object) + self.left_button.pressed.connect(MainWindow.left_button_pressed) + self.right_button.pressed.connect(MainWindow.right_button_pressed) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def __line_widget_line_count_changed(self): + if self.line_number_widget: + n = int(self.editor.document().lineCount()) + self.line_number_widget.changeLineCount(n) + + diff --git a/src/window/main_window.py b/src/window/main_window.py new file mode 100644 index 0000000..661888d --- /dev/null +++ b/src/window/main_window.py @@ -0,0 +1,210 @@ +import os +import sys +import subprocess +import traceback +import platform +import pickle + +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtPrintSupport import * + +from window.draw_object import draw_full_object, draw_object_slider, grid_draw +from window.main.ui_settings import Ui_MainWindow +from window.gcode_export_window import GcodeExportWindow +from window.machine_settings_window import MachineSettingsDialog +from window.app_settings_window import SettingsWindow +from window.main.file_operations import FileOperation + + + +import gcoordinator as gc +import qdarktheme + + +class MainWindow(QMainWindow, Ui_MainWindow): + def __init__(self, parent=None): + super(MainWindow, self).__init__(parent) + self.setupUi(self) + self.new() + grid_draw(self.graphicsView) + self.file_operation = FileOperation() + + def render_execution_result(self): + gc.load_settings('settings/settings.json') + is_completed = self.exec_code(f'python3 -u {main_window.path}') + + if is_completed: + with open('buffer/full_object.pickle', 'rb') as f: + self.full_object = pickle.load(f) + + self.full_object = gc.path_generator.flatten_path_list(self.full_object) + self.graphicsView.clear() + grid_draw(self.graphicsView) + draw_full_object(self.graphicsView, self.full_object) + self.set_sliders() + self.file_save() + self.display_message('object displayed', '#00bfff') + + else: + self.display_message('Error occured while executing the code', '#FF6347') + + def exec_code(self, cmd): + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True) + while True: + line = proc.stdout.readline() + if line: + self.display_message(line.strip(), '#ffffff') + if not line and proc.poll() is not None: + break + + # Check the exit code of the process + return_code = proc.returncode + if return_code == 0: + return True + else: + return False + + def redraw_layer_object(self): + # redraw updated objects according to the layer slider + self.graphicsView.clear() + grid_draw(self.graphicsView) + self.slider_segment.setRange(0, len(self.full_object[self.slider_layer.value()].coords)) + self.slider_segment.setValue( len(self.full_object[self.slider_layer.value()].coords)) + draw_object_slider( self.graphicsView,self.full_object, \ + self.slider_layer.value(),\ + self.slider_segment.value()) + + def redraw_segment_object(self): + # redraw updated objects according to the segment slider + self.graphicsView.clear() + grid_draw(self.graphicsView) + draw_object_slider( self.graphicsView,self.full_object, \ + self.slider_layer.value(),\ + self.slider_segment.value()) + + def set_sliders(self): + self.slider_layer.setRange (0, len(self.full_object)-1) + self.slider_layer.setValue ( len(self.full_object)-1) + self.slider_segment.setRange(0, len(self.full_object[self.slider_layer.value()].coords)) + self.slider_segment.setValue( len(self.full_object[self.slider_layer.value()].coords)) + + def up_button_pressed(self): + self.slider_layer.setValue(self.slider_layer.value()+1) + + def down_button_pressed(self): + self.slider_layer.setValue(self.slider_layer.value()-1) + + def left_button_pressed(self): + self.slider_segment.setValue(self.slider_segment.value()-1) + + def right_button_pressed(self): + self.slider_segment.setValue(self.slider_segment.value()+1) + + def Gcode_create(self): + self.gcode = gc.GCode(self.full_object) + self.gcode.start_gcode('settings/start_gcode.txt') + self.gcode.end_gcode('settings/end_gcode.txt') + self.gcode.save('buffer/G-coordinator.gcode') + self.display_message('Gcode Exported', '#00bfff') + self.gcode_window = GcodeExportWindow() + self.gcode_window.show() + + def open_machine_settings_window(self): + self.machine_settings_dialog = MachineSettingsDialog() + self.machine_settings_dialog.show() + + def settings(self): + settings_window = SettingsWindow() + settings_window.exec_() + self.apply_settings() + + def apply_settings(self): + settings = QSettings('settings/app_settings.ini', QSettings.IniFormat) + theme = settings.value('theme') + editor_font_size = settings.value('editor/font_size') + console_font_size = settings.value('console/font_size') + qdarktheme.setup_theme(theme) + + font_path = 'resources/FiraCode-Regular.ttf' + absolute_font_path = os.path.abspath(font_path) + font_id = QFontDatabase.addApplicationFont(absolute_font_path) + font_family = QFontDatabase.applicationFontFamilies(font_id)[0] + + editor_font = QFont(font_family, int(editor_font_size)) + console_font = QFont(font_family, int(console_font_size)) + self.editor.setFont(editor_font) + self.line_number_widget.setFontSize(int(editor_font_size)) + self.line_number_widget.setFont(editor_font) + self.message_console.setFont(console_font) + + def closeEvent(self, event): + with open('buffer/G-coordinator.gcode', 'w') as file: + file.write('') + event.accept() + + + + + + #================================================================= + # file operartion + def file_open(self): + self.file_operation.open(self) + + def file_save(self): + self.file_operation.save(self) + + def file_save_as(self): + self.file_operation.save_as(self) + + def update_title(self): + # setting window title with prefix as file name + self.setWindowTitle("%s - G-coordinator" %(os.path.basename(self.path) + if self.path else "Untitled")) + + + + + + #================================================================= + # menu bar + def documentation(self): + self.menu_bar.documentation(self) + + def version_info(self): + self.menu_bar.version_info(self) + + def contact_us(self): + self.menu_bar.contact_us(self) + + def run(self): + self.file_save() + self.render_execution_result() + + + + #================================================================= + # other methods + + def display_message(self, message, color): + self.message_console.setTextColor(QColor(color)) + self.message_console.append(message) + self.message_console.moveCursor(QTextCursor.End) + app.processEvents() + + def new(self): + # when you open the G-coordinator, this method is called + # initialize the editor + self.path = None + with open('buffer/default_start.py', 'r') as file: + code_str = file.read() + + self.editor.setPlainText(code_str) + self.update_title() + + +app = QApplication(sys.argv) +main_window = MainWindow() +main_window.apply_settings()