diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml
new file mode 100644
index 0000000..1e7fecd
--- /dev/null
+++ b/.github/workflows/check-docs.yml
@@ -0,0 +1,60 @@
+name: Clippy and Documentation Check
+
+on:
+ pull_request:
+ branches:
+ - main
+ - develop
+ release:
+ types: [published]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Cache Cargo registry and git
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-cargo-registry-
+
+ - name: Cache target directory
+ uses: actions/cache@v3
+ with:
+ path: target
+ key: ${{ runner.os }}-target-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-target-
+
+ - name: Install system dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ curl \
+ wget \
+ file \
+ libssl-dev \
+ libgtk-3-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev
+
+ - name: Set up Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ override: true
+
+ - name: Install Clippy
+ run: rustup component add clippy
+
+ - name: Run Clippy with Documentation Lints
+ run: cargo clippy -- -D warnings
diff --git a/.github/workflows/create-doc-pages.yml b/.github/workflows/create-doc-pages.yml
new file mode 100644
index 0000000..d5ffed1
--- /dev/null
+++ b/.github/workflows/create-doc-pages.yml
@@ -0,0 +1,28 @@
+name: Generate and Deploy Rust Docs
+
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ rust-docs:
+ name: Generate Rust Documentation
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Generate documentation
+ run: cargo doc --no-deps
+
+ - name: Deploy to GitHub Pages
+ if: github.ref == 'refs/heads/main'
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./target/doc
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..64f2d42
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,35 @@
+name: OSCPS_Release_Workflow
+
+on:
+ workflow_dispatch: # Enables manual triggering from GitHub Actions UI
+
+jobs:
+ build:
+ name: Build and Release
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Build release binary
+ run: cargo build --release
+
+ - name: Compress binary
+ run: |
+ mkdir -p release
+ cp target/release/your-binary-name release/
+ cd release
+ tar -czvf your-binary-name-linux.tar.gz your-binary-name
+ shell: bash
+
+ - name: Create GitHub Release and Upload Binary
+ uses: softprops/action-gh-release@v2
+ with:
+ files: release/your-binary-name-linux.tar.gz
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml
new file mode 100644
index 0000000..6bc022b
--- /dev/null
+++ b/.github/workflows/rust-tests.yml
@@ -0,0 +1,63 @@
+name: Rust Tests
+
+on:
+ pull_request:
+ branches:
+ - main
+ - develop
+ release:
+ types: [published]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ rust: [stable]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Cache Cargo registry and git
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-cargo-registry-
+
+ - name: Cache target directory
+ uses: actions/cache@v3
+ with:
+ path: target
+ key: ${{ runner.os }}-target-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-target-
+
+ - name: Install system dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ curl \
+ wget \
+ file \
+ libssl-dev \
+ libgtk-3-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev
+
+ - name: Set up Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.rust }}
+ override: true
+
+ - name: Install Rust dependencies
+ run: cargo build --release
+
+ - name: Run tests
+ run: cargo test --all
diff --git a/.gitignore b/.gitignore
index ea8c4bf..f576422 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
/target
+.DS_Store
+oscps-gui/bun.lockb
+Cargo.lock
diff --git a/Cargo.lock b/Cargo.lock
deleted file mode 100644
index 45c38ca..0000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "oscps"
-version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 5047b14..7d654db 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
-[package]
-name = "oscps"
-version = "0.1.0"
-edition = "2021"
+[workspace]
+resolver = "2"
-[dependencies]
+members = [
+ "oscps-gui",
+ "oscps-lib",
+ ]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
index fe353c7..0427d72 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,13 @@
-# OSCPS
-Open Source Chemical Engineering Process Simulator
\ No newline at end of file
+# OSCPS (Open Source Chemical Process Simulator)
+
+
+
+
+Developing a dynamic & steady-state chemical process simulator using a Rust-based backend and frontend. This project aims to create a much better version of ASPEN that will also be open-sourced.
+
+## Authors
+
+- Bhargav Akula
+- Nathaniel Thomas
+
+
diff --git a/oscps-db/Cargo.toml b/oscps-db/Cargo.toml
new file mode 100644
index 0000000..522b655
--- /dev/null
+++ b/oscps-db/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "oscps-db"
+version = "0.1.0"
+authors = ["Nathaniel Thomas ", "Bhargav Akula "]
+edition = "2021"
+
+[dependencies]
diff --git a/oscps-db/src/lib.rs b/oscps-db/src/lib.rs
new file mode 100644
index 0000000..d8f2f31
--- /dev/null
+++ b/oscps-db/src/lib.rs
@@ -0,0 +1,13 @@
+///#OSCPS-db
+///
+///Main library folder for the OSCPS-db library crate
+///
+///will hold the methods to pull information from the POSTGRES database and from relevant APIs
+///
+///Will hold property information for chemcial property calculation
+///Will hold information about the user's simulation
+
+
+pub mod postgres_db;
+pub mod properties_db;
+
diff --git a/oscps-db/src/postgres_db.rs b/oscps-db/src/postgres_db.rs
new file mode 100644
index 0000000..7bdd500
--- /dev/null
+++ b/oscps-db/src/postgres_db.rs
@@ -0,0 +1,28 @@
+/// #PostgresDB
+///
+/// Will provide methods to connect to a postgres database to pull relevant property and/or
+/// simulation information
+///
+/// properties:
+/// 1. db_name
+/// 2. query
+/// 3. status
+/// 4. connection_key
+
+use sqlx::PgPool;
+use uuid::Uuid;
+
+
+enum DBStatus {
+ Successful,
+ Failure,
+ InProgress
+}
+
+pub struct PostgresDB {
+ pub db_name: String,
+ pub input_query: String,
+ pub request_status: DBStatus,
+ db_key: Uuid,
+ db_pool: PgPool,
+}
diff --git a/oscps-db/src/properties_db.rs b/oscps-db/src/properties_db.rs
new file mode 100644
index 0000000..b011aa2
--- /dev/null
+++ b/oscps-db/src/properties_db.rs
@@ -0,0 +1,4 @@
+///# PropertiesDB
+///
+///Will formulate the right queries required for 'DBConnector'
+///
diff --git a/oscps-gui/Cargo.toml b/oscps-gui/Cargo.toml
new file mode 100644
index 0000000..cf7081c
--- /dev/null
+++ b/oscps-gui/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "oscps-gui"
+version = "0.1.0"
+authors = ["Nathaniel Thomas ", "Bhargav Akula "]
+edition = "2021"
+
+[dependencies]
+env_logger = "0.11.6"
+iced = { version = "0.13.1", features = ["advanced", "canvas", "debug", "lazy"] }
+log = "0.4"
+oscps-lib = { path = "../oscps-lib" }
+strum = "0.27.1"
+strum_macros = "0.27.1"
diff --git a/oscps-gui/src/flowsheet.rs b/oscps-gui/src/flowsheet.rs
new file mode 100644
index 0000000..e214cbf
--- /dev/null
+++ b/oscps-gui/src/flowsheet.rs
@@ -0,0 +1,552 @@
+use iced::mouse;
+use iced::widget::canvas::event::{self, Event};
+use iced::widget::canvas::path::Builder;
+use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
+use iced::{Element, Fill, Point, Rectangle, Renderer, Theme};
+use oscps_lib::simulation::Simulation;
+
+use std::time::{Duration, SystemTime};
+
+use log::{debug, info, warn};
+use strum_macros::Display;
+
+#[derive(Default)]
+pub struct State {
+ cache: canvas::Cache,
+ pub placement_mode: Component,
+}
+
+impl State {
+ pub fn view<'a>(
+ &'a self,
+ components: &'a [Component],
+ simulation: &'a Simulation,
+ ) -> Element<'a, Component> {
+ Canvas::new(Flowsheet {
+ state: self,
+ components,
+ left_click_time: SystemTime::now(),
+ simulation,
+ })
+ .width(Fill)
+ .height(Fill)
+ .height(Fill)
+ .into()
+ }
+ pub fn request_redraw(&mut self) {
+ self.cache.clear();
+ }
+}
+
+#[derive(Display, Debug, Clone, Copy, PartialEq)]
+pub enum Component {
+ Connector {
+ from: Option,
+ to: Option,
+ },
+ Mixer {
+ at: Option,
+ input: Option,
+ output: Option,
+ },
+ Source {
+ at: Option,
+ output: Option,
+ },
+ Sink {
+ at: Option,
+ input: Option,
+ },
+}
+
+impl Component {
+ pub fn connector() -> Self {
+ Component::Connector {
+ from: None,
+ to: None,
+ }
+ }
+
+ pub fn source() -> Self {
+ Component::Source {
+ at: None,
+ output: None,
+ }
+ }
+ pub fn sink() -> Self {
+ Component::Sink {
+ at: None,
+ input: None,
+ }
+ }
+ pub fn mixer() -> Self {
+ Component::Mixer {
+ at: None,
+ input: None,
+ output: None,
+ }
+ }
+
+ // Determine if cursor is within 5 pixels of a given point.
+ fn is_in_bounds(&self, cursor_position: Point, input: Point) -> bool {
+ info!(
+ "Checking input bounds with cursor at ({}, {})",
+ cursor_position.x, cursor_position.y
+ );
+
+ // TODO: (minor) Fix arbitrary 5-pixel bounding box. Make dynamic/program setting.
+ let bound = 5.0;
+ if cursor_position.x > input.x - bound && cursor_position.x < input.x + bound {
+ debug!("Bound x match!");
+ if cursor_position.y > input.y - bound && cursor_position.y < input.y + bound {
+ info!("Bounds match!");
+ return true;
+ }
+ }
+ false
+ }
+
+ // Determine if the cursor is in bounds of the input
+ fn on_input(&self, cursor_position: Point) -> bool {
+ let input = self.get_input();
+ match input {
+ Some(point) => self.is_in_bounds(cursor_position, point),
+ None => false,
+ }
+ }
+
+ // Determine if the cursor is in bounds of the output
+ fn get_input(&self) -> Option {
+ return match self {
+ Component::Connector { from, .. } => *from,
+ Component::Mixer { input, .. } => *input,
+ Component::Sink { input, .. } => *input,
+ Component::Source { .. } => None, // Source does not have an input
+ };
+ }
+
+ fn on_output(&self, cursor_position: Point) -> bool {
+ let output = self.get_output();
+ match output {
+ Some(point) => self.is_in_bounds(cursor_position, point),
+ None => false,
+ }
+ }
+
+ fn get_output(&self) -> Option {
+ return match self {
+ Component::Connector { to, .. } => *to,
+ Component::Mixer { output, .. } => *output,
+ Component::Source { output, .. } => *output,
+ Component::Sink { .. } => None, // Sink does not have an output
+ };
+ }
+
+ fn draw_all(components: &[Component], frame: &mut Frame, theme: &Theme) {
+ // TODO: (minor) Nitpicky, but this uses dynamic memory uncessesarily.
+ // Consider changing the function name fetching to a macro approach.
+ let function_name = std::any::type_name::()
+ .split("::")
+ .last()
+ .unwrap_or("unknown");
+ let expect_string = format!(
+ "{} should should only be called with existing points.",
+ function_name
+ );
+ let components = Path::new(|p| {
+ for component in components {
+ match component {
+ Component::Connector { from, to } => {
+ let from = from.expect(&expect_string);
+ let to = to.expect(&expect_string);
+
+ Component::draw_connector(p, to, from)
+ }
+ Component::Mixer { at, input, output } => {
+ let at = at.expect(&expect_string);
+ let input = input.expect(&expect_string);
+ let output = output.expect(&expect_string);
+
+ Component::draw_mixer(p, at, input, output)
+ }
+ Component::Source { at, output } => {
+ let at = at.expect(&expect_string);
+ let output = output.expect(&expect_string);
+
+ Component::draw_source(p, at, output)
+ }
+ Component::Sink { at, input } => {
+ let at = at.expect(&expect_string);
+ let input = input.expect(&expect_string);
+
+ Component::draw_sink(p, at, input)
+ }
+ }
+ }
+ });
+
+ frame.stroke(
+ &components,
+ Stroke::default()
+ .with_width(2.0)
+ .with_color(theme.palette().text),
+ );
+ }
+
+ pub fn draw_connector(p: &mut Builder, to: Point, from: Point) {
+ debug!("Drawing connector");
+ p.move_to(from);
+ let half_x_coord = from.x + (to.x - from.x) / 2.0;
+ p.line_to(Point::new(half_x_coord, from.y));
+ p.line_to(Point::new(half_x_coord, to.y));
+ p.line_to(Point::new(to.x, to.y));
+ let mut arrow_offset_x = -10.0;
+ let arrow_offset_y = 5.0;
+ if to.x < from.x {
+ arrow_offset_x *= -1.0;
+ }
+ p.line_to(Point::new(to.x + arrow_offset_x, to.y + arrow_offset_y));
+ p.line_to(Point::new(to.x + arrow_offset_x, to.y - arrow_offset_y));
+ p.line_to(Point::new(to.x, to.y));
+ }
+
+ pub fn draw_mixer(p: &mut Builder, at: Point, input: Point, output: Point) {
+ debug!("Drawing mixer.");
+
+ p.move_to(at);
+ let bottom_point = Point::new(at.x, at.y + 100.0);
+ let middle_point = Point::new(at.x + 100.0, at.y + 50.0);
+ p.line_to(bottom_point);
+ p.line_to(middle_point);
+ p.line_to(at);
+
+ // Draw a circle for input connectors
+ p.move_to(at);
+ p.circle(input, 5.0);
+ // Another circle for output connectors
+ p.move_to(at);
+ p.circle(output, 5.0);
+ }
+
+ pub fn draw_source(p: &mut Builder, at: Point, output: Point) {
+ debug!("Drawing source.");
+
+ p.move_to(at);
+ p.rectangle(at, (50.0, 100.0).into());
+
+ // Circle for output
+ p.circle(output, 5.0);
+ }
+
+ pub fn draw_sink(p: &mut Builder, at: Point, input: Point) {
+ debug!("Drawing sink.");
+
+ p.move_to(at);
+ p.rectangle(at, (50.0, 100.0).into());
+
+ // Circle for input
+ p.move_to(at);
+ p.circle(input, 5.0);
+ }
+}
+
+// Declare the default block to be the humble connector.
+impl Default for Component {
+ fn default() -> Self {
+ Component::Connector {
+ from: None,
+ to: None,
+ }
+ }
+}
+
+struct Flowsheet<'a> {
+ state: &'a State,
+ components: &'a [Component],
+ left_click_time: SystemTime,
+ simulation: &'a Simulation,
+}
+
+impl<'a> Flowsheet<'a> {
+ fn place_sink(cursor_position: Point) -> Option {
+ let input = Point::new(cursor_position.x - 5.0, cursor_position.y + 50.0);
+ info!(
+ "Creating source at ({}, {}) with input at ({}, {}).",
+ cursor_position.x, cursor_position.y, input.x, input.y
+ );
+ Some(Component::Sink {
+ at: Some(cursor_position),
+ input: Some(input),
+ })
+ }
+
+ fn place_source(cursor_position: Point) -> Option {
+ let output = Point::new(cursor_position.x + 55.0, cursor_position.y + 50.0);
+ info!(
+ "Creating source at ({}, {}) with output at ({}, {}).",
+ cursor_position.x, cursor_position.y, output.x, output.y
+ );
+ Some(Component::Source {
+ at: Some(cursor_position),
+ output: Some(output),
+ })
+ }
+
+ // Helper function to place a mixer block
+ fn place_mixer(cursor_position: Point) -> Option {
+ let input = Point::new(cursor_position.x - 5.0, cursor_position.y + 50.0);
+ let output = Point::new(cursor_position.x + 105.0, cursor_position.y + 50.0);
+ info!(
+ "Creating mixer at ({}, {}) with input at ({}, {}) and output at ({}, {})",
+ cursor_position.x, cursor_position.y, input.x, input.y, output.x, output.y
+ );
+ Some(Component::Mixer {
+ at: Some(cursor_position),
+ input: Some(input),
+ output: Some(output),
+ })
+ }
+
+ // Helper function to connect a connector to an input/output.
+ fn place_connector(
+ &self,
+ state: &mut Option,
+ cursor_position: Point,
+ ) -> Option {
+ let floating_connectors = false; // HACK: Disallow floating connectors
+ match state {
+ None => {
+ info!("Beginning creation of connector...");
+ let mut result = Some(Pending::One {
+ from: cursor_position,
+ });
+
+ for component in self.components {
+ if !matches!(component, Component::Connector { .. })
+ && component.on_output(cursor_position)
+ {
+ info!("Connected to input!");
+ result = Some(Pending::One {
+ // NOTE: Should be safe. This must be Some(..) if
+ // on_output returned true.
+ from: component.get_output().unwrap(),
+ });
+ *state = result;
+ return None;
+ }
+ }
+ if floating_connectors {
+ *state = result;
+ }
+ None
+ }
+ Some(Pending::One { from }) => {
+ info!("Created connector.");
+ let from = *from;
+ let mut result = Some(Component::Connector {
+ from: Some(from),
+ to: Some(cursor_position),
+ });
+ for component in self.components {
+ if !matches!(component, Component::Connector { .. })
+ && component.on_input(cursor_position)
+ {
+ info!("Connected to input!");
+ result = Some(Component::Connector {
+ from: Some(from),
+ // NOTE: Should be safe, on_input() returned true.
+ to: Some(component.get_input().unwrap()),
+ });
+ *state = None;
+ return result;
+ }
+ }
+ if floating_connectors {
+ *state = None;
+ result
+ } else {
+ None
+ }
+ }
+ }
+ }
+}
+
+impl<'a> canvas::Program for Flowsheet<'a> {
+ type State = Option;
+
+ fn update(
+ &self,
+ state: &mut Self::State,
+ event: Event,
+ bounds: Rectangle,
+ cursor: mouse::Cursor,
+ ) -> (event::Status, Option) {
+ let Some(cursor_position) = cursor.position_in(bounds) else {
+ return (event::Status::Ignored, None);
+ };
+ match event {
+ Event::Mouse(mouse_event) => {
+ let message = match mouse_event {
+ mouse::Event::ButtonPressed(mouse::Button::Left) => {
+ info!("Click detected at ({})", cursor_position);
+ let current_time = SystemTime::now();
+
+ match current_time.duration_since(self.left_click_time) {
+ Ok(elapsed) => {
+ if elapsed < Duration::from_millis(200) {
+ println!("Double click!")
+ }
+ }
+ Err(e) => {
+ warn!("Error {} when detecting double click.", e)
+ }
+ }
+
+ match self.state.placement_mode {
+ Component::Connector { .. } => {
+ Flowsheet::place_connector(&self, state, cursor_position)
+ }
+ Component::Mixer { .. } => {
+ self.simulation;
+ Flowsheet::place_mixer(cursor_position)
+ }
+ Component::Source { .. } => Flowsheet::place_source(cursor_position),
+ Component::Sink { .. } => Flowsheet::place_sink(cursor_position),
+ }
+ }
+ // Right click should cancel placement.
+ mouse::Event::ButtonPressed(mouse::Button::Right) => {
+ info!("Right mouse button clicked");
+ *state = None;
+ None
+ }
+ _ => None,
+ };
+
+ (event::Status::Captured, message)
+ }
+ Event::Keyboard(_) => (event::Status::Captured, None),
+ _ => (event::Status::Ignored, None),
+ }
+ }
+
+ fn draw(
+ &self,
+ state: &Self::State,
+ renderer: &Renderer,
+ theme: &Theme,
+ bounds: Rectangle,
+ cursor: mouse::Cursor,
+ ) -> Vec {
+ let content = self.state.cache.draw(renderer, bounds.size(), |frame| {
+ Component::draw_all(self.components, frame, theme);
+ // Border frame
+ frame.stroke(
+ &Path::rectangle(Point::ORIGIN, frame.size()),
+ Stroke::default()
+ .with_width(10.0)
+ .with_color(theme.palette().text),
+ );
+ });
+ if let Some(pending) = state {
+ vec![content, pending.draw(renderer, theme, bounds, cursor)] // Connector being drawn
+ } else {
+ vec![content] // Just draw current content.
+ }
+ }
+
+ fn mouse_interaction(
+ &self,
+ state: &Self::State,
+ bounds: Rectangle,
+ cursor: mouse::Cursor,
+ ) -> mouse::Interaction {
+ let Some(cursor_position) = cursor.position_in(bounds) else {
+ return mouse::Interaction::default();
+ };
+
+ // Only display a grab icon if placing a connector, and
+ // the connector is hovering over an input when in input mode, or over
+ // an output when in output mode.
+ if cursor.is_over(bounds) {
+ match self.state.placement_mode {
+ Component::Connector { .. } => {
+ for component in self.components {
+ match component {
+ Component::Connector { .. } => (),
+ _ => match state {
+ Some(Pending::One { .. }) => {
+ if component.on_input(cursor_position) {
+ println!("Some");
+ return mouse::Interaction::Grab;
+ }
+ }
+ None => {
+ if component.on_output(cursor_position) {
+ println!("Component: {}", component);
+ println!("None");
+ return mouse::Interaction::Grab;
+ }
+ }
+ },
+ }
+ }
+ mouse::Interaction::Crosshair
+ }
+ _ => mouse::Interaction::Crosshair,
+ }
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Pending {
+ One { from: Point },
+}
+
+impl Pending {
+ fn draw(
+ &self,
+ renderer: &Renderer,
+ theme: &Theme,
+ bounds: Rectangle,
+ cursor: mouse::Cursor,
+ ) -> Geometry {
+ let mut frame = Frame::new(renderer, bounds.size());
+
+ if let Some(cursor_position) = cursor.position_in(bounds) {
+ match *self {
+ Pending::One { from } => {
+ let to = cursor_position;
+ let line = Path::new(|p| {
+ p.move_to(from);
+ let half_x_coord = from.x + (to.x - from.x) / 2.0;
+ p.line_to(Point::new(half_x_coord, from.y));
+ p.line_to(Point::new(half_x_coord, to.y));
+ p.line_to(Point::new(to.x, to.y));
+
+ let mut arrow_offset_x = -10.0;
+ let arrow_offset_y = 5.0;
+ if to.x < from.x {
+ arrow_offset_x *= -1.0;
+ }
+ p.line_to(Point::new(to.x + arrow_offset_x, to.y + arrow_offset_y));
+ p.line_to(Point::new(to.x + arrow_offset_x, to.y - arrow_offset_y));
+ p.line_to(Point::new(to.x, to.y));
+ });
+ frame.stroke(
+ &line,
+ Stroke::default()
+ .with_width(2.0)
+ .with_color(theme.palette().text),
+ );
+ }
+ };
+ }
+
+ frame.into_geometry()
+ }
+}
diff --git a/oscps-gui/src/main.rs b/oscps-gui/src/main.rs
new file mode 100644
index 0000000..19d8a6c
--- /dev/null
+++ b/oscps-gui/src/main.rs
@@ -0,0 +1,340 @@
+mod flowsheet;
+mod style;
+
+use iced::widget::pane_grid::{self, PaneGrid};
+use iced::widget::{button, column, container, horizontal_space, hover, responsive, text};
+use iced::{Center, Element, Fill, Length, Theme};
+
+use oscps_lib::simulation::{self, Settings, Simulation};
+
+use icon::Icon;
+
+use log::{debug, info};
+
+pub fn main() -> iced::Result {
+ // Start the GUI env_logger::init();
+ info!("Starting application");
+
+ let mut settings = iced::window::Settings::default();
+ settings.size = (1920.0, 1080.0).into();
+ settings.min_size = Some((480.0, 720.0).into());
+
+ let application = iced::application(
+ "Open Source Chemical Process Simulator",
+ MainWindow::update,
+ MainWindow::view,
+ )
+ .window(settings)
+ .theme(|_| Theme::CatppuccinMocha)
+ .antialiasing(true)
+ .centered();
+
+ application.run()
+}
+
+// These are the structures which make up the main window
+#[allow(dead_code)]
+struct MainWindow {
+ // theme: Theme,
+ panes: pane_grid::State,
+ focus: Option,
+ flowsheet: flowsheet::State,
+ components: Vec,
+ simulation: Simulation,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ AddedComponent(flowsheet::Component),
+ Clear,
+ PlaceComponent(flowsheet::Component),
+ Clicked(pane_grid::Pane),
+ Dragged(pane_grid::DragEvent),
+ Resized(pane_grid::ResizeEvent),
+}
+
+impl MainWindow {
+ fn new() -> Self {
+ let (mut panes, pane) = pane_grid::State::new(Pane::new_selection());
+ if let Some((_, split)) = panes.split(pane_grid::Axis::Vertical, pane, Pane::new_canvas()) {
+ panes.resize(split, 0.2);
+ }
+
+ let settings = Settings::default();
+
+ MainWindow {
+ // theme: Theme::default(),
+ panes,
+ focus: None,
+ flowsheet: flowsheet::State::default(),
+ components: Vec::default(),
+ simulation: Simulation::new(settings),
+ }
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::AddedComponent(component) => {
+ info!("Added component");
+ self.components.push(component);
+ self.flowsheet.request_redraw();
+ match component {
+ flowsheet::Component::Source{ ..} => todo!(),
+ flowsheet::Component::Sink{ ..} => todo!(),
+ flowsheet::Component::Mixer{ ..} => {
+ self.simulation.add_block(simulation::BlockType::Mixer);
+ },
+ flowsheet::Component::Connector{ .. } => {
+ // self.simulation.add_stream(simulation::BlockType::Mixer);
+ todo!();
+ },
+ }
+ }
+ // TODO: Make the clear option more deliberate (2 clicks at least)
+ Message::Clear => {
+ self.flowsheet = flowsheet::State::default();
+ self.components.clear();
+ }
+ // Default placement mode should be 'None'
+ Message::PlaceComponent(component) => {
+ match component {
+ // TODO: Modify to do more work other than a simple assignment.
+ flowsheet::Component::Connector { .. } => {
+ info!("Setting to connector placement mode.");
+ self.flowsheet.placement_mode = flowsheet::Component::connector();
+ }
+ flowsheet::Component::Mixer { .. } => {
+ info!("Setting to mixer placement mode.");
+ self.flowsheet.placement_mode = flowsheet::Component::mixer();
+ }
+ flowsheet::Component::Source { .. } => {
+ info!("Setting to source placement mode.");
+ self.flowsheet.placement_mode = flowsheet::Component::source();
+ }
+ flowsheet::Component::Sink { .. } => {
+ info!("Setting to sink placement mode.");
+ self.flowsheet.placement_mode = flowsheet::Component::sink();
+ }
+ }
+ }
+ Message::Clicked(pane) => {
+ self.focus = Some(pane);
+ info!("You clicked on a pane!")
+ }
+ Message::Dragged(pane_grid::DragEvent::Dropped { pane, target }) => {
+ self.panes.drop(pane, target);
+ info!("You dragged a pane!")
+ }
+ Message::Dragged(_) => {
+ info!("You dragged, but did not drop a pane!")
+ }
+ Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
+ self.panes.resize(split, ratio);
+ info!("You resized a pane!")
+ }
+ }
+ }
+
+ // Create a button to add a certain component
+ fn placement_button<'a>(
+ &'a self,
+ target_mode: flowsheet::Component,
+ ) -> impl Into> {
+ container(
+ button(container(column![
+ Icon::new(target_mode),
+ text(target_mode.to_string())
+ ]))
+ .style(match self.flowsheet.placement_mode {
+ mode if mode == target_mode => button::danger,
+ _ => button::secondary,
+ })
+ .on_press(Message::PlaceComponent(target_mode)),
+ )
+ }
+
+ fn view(&self) -> Element {
+ let focus = self.focus;
+ let pane_grid = PaneGrid::new(&self.panes, |id, pane, _is_maximized| {
+ let is_focused = focus == Some(id);
+ match pane {
+ Pane::ComponentSelection => {
+ debug!("Found Selection!");
+ return column![
+ container(text("Component Selection"))
+ .padding(5)
+ .width(Length::Fill)
+ .style(if is_focused {
+ style::title_bar_focused
+ } else {
+ style::title_bar_active
+ }),
+ self.placement_button(flowsheet::Component::source()).into(),
+ self.placement_button(flowsheet::Component::sink()).into(),
+ self.placement_button(flowsheet::Component::connector())
+ .into(),
+ self.placement_button(flowsheet::Component::mixer()).into(),
+ ]
+ .width(Length::Fill)
+ .into();
+ }
+ Pane::Canvas => {
+ debug!("Found canvas!");
+
+ let flowsheet_title_bar = pane_grid::TitleBar::new("Flowsheet")
+ .padding(10)
+ .style(if is_focused {
+ style::title_bar_focused
+ } else {
+ style::title_bar_active
+ });
+
+ pane_grid::Content::new(responsive(move |_size| {
+ view_content(hover(
+ self.flowsheet
+ .view(&self.components, &self.simulation)
+ .map(Message::AddedComponent),
+ if self.components.is_empty() {
+ container(horizontal_space())
+ } else {
+ container(
+ button("Clear")
+ .style(button::danger)
+ .on_press(Message::Clear),
+ )
+ .padding(10)
+ .align_top(Fill)
+ },
+ ))
+ }))
+ .title_bar(flowsheet_title_bar)
+ .style(if is_focused {
+ style::pane_focused
+ } else {
+ style::pane_active
+ })
+ }
+ }
+ })
+ .width(Fill)
+ .height(Fill)
+ .spacing(10)
+ .on_click(Message::Clicked)
+ .on_drag(Message::Dragged)
+ .on_resize(10, Message::Resized);
+
+ container(column![pane_grid,]).padding(20).into()
+ }
+}
+
+impl Default for MainWindow {
+ fn default() -> Self {
+ MainWindow::new()
+ }
+}
+
+mod icon {
+ use crate::flowsheet;
+ use iced::advanced::layout::{self, Layout};
+ use iced::advanced::renderer;
+ use iced::advanced::widget::{self, Widget};
+ use iced::border;
+ use iced::mouse;
+ use iced::{Color, Element, Length, Rectangle, Size};
+
+ pub struct Icon {
+ // component: flowsheet::Component,
+ }
+
+ impl Icon {
+ pub fn new(_component: flowsheet::Component) -> Self {
+ Self {
+ // component
+ }
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn icon(component: flowsheet::Component) -> Icon {
+ Icon::new(component)
+ }
+
+ impl Widget for Icon
+ where
+ Renderer: renderer::Renderer,
+ {
+ fn size(&self) -> Size {
+ Size {
+ width: Length::Shrink,
+ height: Length::Shrink,
+ }
+ }
+
+ fn layout(
+ &self,
+ _tree: &mut widget::Tree,
+ _renderer: &Renderer,
+ _limits: &layout::Limits,
+ ) -> layout::Node {
+ let hard_size = 100.0; // HACK: Temporary, figure out a more elegant solution later.
+ layout::Node::new(Size::new(hard_size, hard_size))
+ }
+
+ fn draw(
+ &self,
+ _state: &widget::Tree,
+ renderer: &mut Renderer,
+ _theme: &Theme,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor: mouse::Cursor,
+ _viewport: &Rectangle,
+ ) {
+ let hard_size = 50.0; // HACK: Again, temporary
+
+ // TODO: Placeholder for when custom widgets have better support.
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: layout.bounds(),
+ border: border::rounded(hard_size),
+ ..renderer::Quad::default()
+ },
+ Color::BLACK,
+ );
+ }
+ }
+ impl From for Element<'_, Message, Theme, Renderer>
+ where
+ Renderer: renderer::Renderer,
+ {
+ fn from(icon: Icon) -> Self {
+ Self::new(icon)
+ }
+ }
+}
+
+#[derive(Clone, Copy, Default)]
+enum Pane {
+ Canvas,
+
+ #[default]
+ ComponentSelection,
+}
+
+impl Pane {
+ fn new_selection() -> Self {
+ Pane::ComponentSelection
+ }
+ fn new_canvas() -> Self {
+ Pane::Canvas
+ }
+}
+
+fn view_content<'a>(flowsheet: Element<'a, Message>) -> Element<'a, Message> {
+ let content = column![flowsheet] // controls,
+ .spacing(10)
+ .align_x(Center);
+
+ container(content).center_y(Fill).padding(5).into()
+}
diff --git a/oscps-gui/src/style.rs b/oscps-gui/src/style.rs
new file mode 100644
index 0000000..3cbdb64
--- /dev/null
+++ b/oscps-gui/src/style.rs
@@ -0,0 +1,50 @@
+use iced::widget::container;
+use iced::{Border, Theme};
+
+pub fn title_bar_active(theme: &Theme) -> container::Style {
+ let palette = theme.extended_palette();
+
+ container::Style {
+ text_color: Some(palette.background.strong.text),
+ background: Some(palette.background.strong.color.into()),
+ ..Default::default()
+ }
+}
+
+pub fn title_bar_focused(theme: &Theme) -> container::Style {
+ let palette = theme.extended_palette();
+
+ container::Style {
+ text_color: Some(palette.primary.strong.text),
+ background: Some(palette.primary.strong.color.into()),
+ ..Default::default()
+ }
+}
+
+pub fn pane_active(theme: &Theme) -> container::Style {
+ let palette = theme.extended_palette();
+
+ container::Style {
+ background: Some(palette.background.weak.color.into()),
+ border: Border {
+ width: 2.0,
+ color: palette.background.strong.color,
+ ..Border::default()
+ },
+ ..Default::default()
+ }
+}
+
+pub fn pane_focused(theme: &Theme) -> container::Style {
+ let palette = theme.extended_palette();
+
+ container::Style {
+ background: Some(palette.background.weak.color.into()),
+ border: Border {
+ width: 2.0,
+ color: palette.primary.strong.color,
+ ..Border::default()
+ },
+ ..Default::default()
+ }
+}
diff --git a/oscps-lib/Cargo.toml b/oscps-lib/Cargo.toml
new file mode 100644
index 0000000..aef0dad
--- /dev/null
+++ b/oscps-lib/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "oscps-lib"
+version = "0.1.0"
+authors = ["Nathaniel Thomas ", "Bhargav Akula "]
+edition = "2021"
+
+[dependencies]
+uom = "0.37.0"
+once_cell = "1.17.1"
+pubchem = "0.1.1"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+tokio = { version = "1", features = ["full"] }
+anyhow = "1.0"
+autodiff = "0.7.0"
+oscps-db = { path = "../oscps-db" }
diff --git a/oscps-lib/src/blocks.rs b/oscps-lib/src/blocks.rs
new file mode 100644
index 0000000..4206fcd
--- /dev/null
+++ b/oscps-lib/src/blocks.rs
@@ -0,0 +1,307 @@
+//! # Blocks
+//!
+//! This file contains traits implemented by various structs to represent
+//! different unit operations.
+//!
+//! For example, if a block is a simple mixer, then it will implement the
+//! MassBalance trait but not the EnergyBalance.
+
+use crate::stream::Stream;
+use once_cell::sync::Lazy;
+use uom::si::energy::joule;
+use uom::si::f64::Energy;
+use uom::si::f64::Mass;
+use uom::si::mass::kilogram;
+
+use crate::simulation::StreamReference;
+
+/// # Block
+///
+/// A trait that all blocks must implement.
+/// TODO: In ASPEN, streams can be used to specify process inputs and outputs.
+/// Instead, have special blocks that are 'source' and 'sink' blocks for
+/// material entering and exiting the simulation. To make it more user friendly,
+/// if a user attempts to run a simulation with stream that are not connected to
+/// inputs or outputs, offer to automatically insert sources/sinks where the loose
+/// ends are. While these special blocks will still have to implement this trait
+/// (and thus implement unnecessary functions, such as the "connect_input" function
+/// for a souce block, these functions can simply be dummy functions for this special case.
+/// For safety, they can throw errors if called, but they should never be used.
+pub trait Block {
+ /// Connect an input to a block. TODO: Have this function create the input stream and return a
+ /// reference to it. Then use that stream reference to connect an output.
+ fn connect_input(&mut self, stream: &mut Stream) -> Result<(), &str>;
+ /// Disconnect an input to a block
+ fn disconnect_input(&mut self, stream: &mut Stream) -> Result<(), &str>;
+ /// Connect an output to a block
+ fn connect_output(&mut self, stream: &mut Stream) -> Result<(), &str>;
+ /// Disconnect an output to a block
+ fn disconnect_output(&mut self, stream: &mut Stream) -> Result<(), &str>;
+ // TODO: Add additional functions that all Blocks should implement
+}
+
+/// # Separator
+///
+/// A Separator block that allows components of a stream to be separated.
+/// Allows for a single input and an arbitrary number of outputs.
+#[allow(dead_code)]
+struct Separator {
+ id: u64,
+ input: Option, // An option is used in case there is no input stream
+ outputs: Vec, // An empty vec can represent no outputs, no need for Option>>
+ // TODO: Add additional fields that controls how components are separated
+}
+
+#[allow(dead_code)]
+impl Separator {
+ fn new(id: u64) -> Self {
+ Separator {
+ id,
+ input: None,
+ outputs: Vec::new(),
+ }
+ }
+}
+
+#[allow(dead_code)]
+/// # Mixer
+///
+/// A block used for simple stream mixing operations. Spacial information
+/// is not stored in the case that non-gui applications use this backend.
+pub struct Mixer {
+ /// Set of inlet streams for the mixer
+ pub inputs: Option>,
+ /// Outlet stream for the mixer block
+ pub output: Option,
+}
+
+#[allow(dead_code)]
+/// Implementations of the mixer block.
+impl Mixer {
+ /// Create a new mixer block. TODO: Figure out importance of lifetimes
+ pub fn new() -> Mixer {
+ Mixer {
+ inputs: None,
+ output: None,
+ }
+ }
+
+ // TODO: Uncomment once desired base functionality is achieved
+ // /// Execute the mixer block (calculate balances, output streams, etc) /// This function still needs to be implemented pub fn execute_block(&mut self) {
+ // self.outlet_stream = Some(connector::Stream {
+ // s_id: String::from("Mass_Outlet"),
+ // thermo: None,
+ // from_block: String::from("M1"),
+ // to_block: String::from("M2")
+ // // m_flow_total: self.compute_total_outlet_mass_flow().unwrap(),
+ // });
+ // // self.outlet_stream_energy = Some(connector::Econnector {
+ // // e_conn_id: String::from("Energy Outlet"),
+ // // energy_flow_total: self.compute_outlet_energy_flows().unwrap(),
+ // // });
+ // }
+
+ // /// This private method will compute the outlet mass flows for the mixer block
+ // ///
+ // /// # Returns
+ // ///
+ // /// A Mass quantity (uom object) that holds the outlet mass flow
+ // fn compute_total_outlet_mass_flow(&self) -> Option {
+ // // TODO: steps to implement function:
+ // // Need to loop through each of the connector structures and add up the mass flows
+ // // During this process, need to make sure that all the mass flows are in the same units
+ // // Use the UOM package to help with this part...
+ // let mut mass_flow_sum: f64 = 0.0;
+
+ // for s in self.inlet_streams.iter() {
+ // mass_flow_sum += s.thermo.as_ref().unwrap().total_mass();
+ // }
+ // Some(mass_flow_sum)
+ // }
+
+ // /// Determines the total energy flowing through the block
+ // fn compute_outlet_energy_flows(&self) -> Option {
+ // let mut energy_flow_sum: f64 = 0.0;
+
+ // for s in self.inlet_streams.iter() {
+ // energy_flow_sum += s.thermo.as_ref().unwrap().enthalpy();
+ // }
+ // Some(energy_flow_sum)
+ // }
+
+ // /// Determines the phase fractions of the output using thermodynamics.
+ // /// TODO: Implement this function
+ // fn compute_outlet_phase_fractions(&self) {}
+
+ // /// Computes the outlet temperature of the mixer (assumes no chemical
+ // /// reactions) TODO: Implement this function
+ // fn compute_outlet_temperature(&self) {}
+
+ // /// Computes the mixer outlet pressure.
+ // /// TODO: Implement this function
+ // fn compute_outlet_pressure(&self) {}
+}
+
+impl Block for Mixer {
+ fn connect_input<'a>(&mut self, _stream: &mut Stream) -> Result<(), &'static str> {
+ // TODO: Figure out how to store references to streams
+ // self.inputs.push(stream);
+ Ok(())
+ }
+
+ fn disconnect_input(&mut self, _stream: &mut Stream) -> Result<(), &'static str> {
+ Ok(())
+ }
+
+ fn connect_output(&mut self, _stream: &mut Stream) -> Result<(), &'static str> {
+ Ok(())
+ }
+
+ fn disconnect_output(&mut self, _stream: &mut Stream) -> Result<(), &'static str> {
+ Ok(())
+ }
+}
+
+#[allow(dead_code)]
+/// Minimum error allowed for energy difference.
+/// TODO: Change this to a relative scale instead of an absolute scale.
+pub static TOLERENCE_ENERGY: Lazy = Lazy::new(|| Energy::new::(5.0));
+
+#[allow(dead_code)]
+/// Minimum error allowed for mass difference.
+/// TODO: Change this to a relative scale instead of an absolute scale.
+pub static TOLERENCE_MASS: Lazy = Lazy::new(|| Mass::new::(5.0));
+
+#[allow(dead_code)]
+/// # MassBalance
+///
+/// Trait for ensuring the overall mass balance is maintained in a flowsheet.
+///
+/// This trait can be implemented by any block that needs to ensure mass
+/// conservation.
+pub trait MassBalance {
+ /// Perform a mass balance check on object by comparing inlet and outlet
+ /// mass. TODO: Compare mass flow rates, not mass and check for relative
+ /// error instead of absolute, perhaps error should be less than 1e-6
+ /// fraction of the total inlet mass. This can be an adjustable parameter.
+ /// Smaller takes longer to converge, but is more
+ fn mass_balance_check(&self, mass_in: Mass, mass_out: Mass) -> bool {
+ let mass_in_kg = mass_in.get::();
+ let mass_out_kg = mass_out.get::();
+ let mass_difference = mass_in_kg - mass_out_kg;
+ mass_difference <= TOLERENCE_MASS.get::()
+ }
+}
+
+#[allow(dead_code)]
+/// # EnergyBalance
+///
+/// This trait ensures that blocks in the flowsheet adhere to energy
+/// conservation principles.
+pub trait EnergyBalance {
+ /// Perform an energy balance on a block. Checks all input and output
+ /// streams and ensures that energy stays the same. TODO: Ensure that
+ /// energy loss is accounted for. For example, a mixer may not be entirely
+ /// adiabatic, and therefor some energy will be lost to the environment.
+ /// Also implement changes in issue #19.
+ fn energy_balance_check(&self, energy_in: Energy, energy_out: Energy) -> bool {
+ let energy_in_joules = energy_in.get::();
+ let energy_out_joules = energy_out.get::();
+ let energy_difference = energy_in_joules - energy_out_joules;
+ energy_difference <= TOLERENCE_ENERGY.get::()
+ }
+}
+
+/// Applying mass balance trait to Mixer Block
+impl MassBalance for Mixer {}
+
+/// Applying the energy balance trait to the Mixer Block
+impl EnergyBalance for Mixer {}
+
+/// Block implements Clone
+impl Clone for Box {
+ fn clone(&self) -> Self {
+ todo!();
+ }
+}
+
+/// # Block Tests
+///
+/// The following module holds all the unit test cases for the blocks module
+#[cfg(test)]
+mod block_tests {
+ // use crate::connector::Stream;
+
+ // use super::*;
+ // use uom::si::energy::kilojoule;
+ // use uom::si::f64::Energy;
+ // use uom::si::mass::pound;
+
+ // #[test]
+ // /// checks whether the mass balance check function was implemented properly
+ // fn test_mass_balance_check_steady_state_for_mixer() {
+ // // here you will need to check that the mass into the mixer = mass out of mixer
+
+ // let mixer_test_obj = Mixer {
+ // block_id: String::from("Test Mixer"),
+ // y_cord: 0,
+ // inlet_streams: Vec::new(),
+ // outlet_stream: None,
+ // };
+ // let mass_in = Mass::new::(100.0);
+ // let mass_out = Mass::new::(95.0);
+ // assert!(mixer_test_obj.mass_balance_check(mass_in, mass_out));
+ // }
+
+ // #[test]
+ // /// checks if the 'energy_balance_check' function was implemented properly
+ // fn test_energy_balance_check_steady_state_for_mixer() {
+ // // energy into mixer = energy out of mixer
+ // let mixer_test_obj = Mixer {
+ // block_id: String::from("Test Mixer"),
+ // x_cord: 0,
+ // y_cord: 0,
+ // inlet_streams: Vec::new(),
+ // outlet_stream: None,
+ // };
+ // let energy_in = Energy::new::(10.0);
+ // let energy_out = Energy::new::(95.0);
+ // assert!(mixer_test_obj.energy_balance_check(energy_in, energy_out));
+ // }
+
+ // #[test]
+ // checking functionality of 'compute_total_outlet_mass_flow'
+ // fn test_compute_total_outlet_mass_flow() {
+ // let in_streams_mass = vec![
+ // Mconnector {
+ // m_conn_id: String::from("Mass1"),
+ // m_flow_total: 3.0,
+ // },
+ // Mconnector {
+ // m_conn_id: String::from("Mass2"),
+ // m_flow_total: 7.0,
+ // },
+ // ];
+ // let mixer = Mixer::new(String::from("Mixer3"), 0, 0, in_streams_mass, vec![]);
+
+ // assert_eq!(mixer.compute_total_outlet_mass_flow(), Some(10.0));
+ // }
+
+ // #[test]
+ // /// checking functionality of 'compute_outlet_energy_flows'
+ // fn test_compute_outlet_energy_flows() {
+ // let in_streams_energy = vec![
+ // Econnector {
+ // e_conn_id: String::from("Energy1"),
+ // energy_flow_total: 100.0,
+ // },
+ // Econnector {
+ // e_conn_id: String::from("Energy2"),
+ // energy_flow_total: 200.0,
+ // },
+ // ];
+ // let mixer = Mixer::new(String::from("Mixer5"), 0, 0, vec![], in_streams_energy);
+
+ // assert_eq!(mixer.compute_outlet_energy_flows(), Some(300.0));
+ // }
+}
diff --git a/oscps-lib/src/lib.rs b/oscps-lib/src/lib.rs
new file mode 100644
index 0000000..7a42da1
--- /dev/null
+++ b/oscps-lib/src/lib.rs
@@ -0,0 +1,17 @@
+//! # oscps-lib
+//! 'oscps-lib' is a library which provides the functionality required to build
+//! a chemical process simulator. It includes both dynamic and steady-state
+//! simulation capabilities.
+
+#![warn(missing_docs)]
+
+extern crate uom;
+extern crate once_cell;
+extern crate serde;
+extern crate anyhow;
+
+pub mod blocks;
+pub mod component;
+pub mod simulation;
+pub mod stream;
+pub mod thermodynamics;
diff --git a/oscps-lib/src/properties.rs b/oscps-lib/src/properties.rs
new file mode 100644
index 0000000..73b9720
--- /dev/null
+++ b/oscps-lib/src/properties.rs
@@ -0,0 +1,107 @@
+//! # Properties
+//!
+//! Contains chemical properties for species in the simulation.
+
+extern crate uom;
+extern crate pubchem;
+///Importing pure species properties
+pub mod pure_species_properties;
+
+use anyhow::Result;
+use uom::si::f64::*;
+use std::{thread,time::Duration};
+use serde::{Serialize, Deserialize};
+
+#[allow(dead_code)]
+/// Used by the "Chemical" struct to create the pubchem::Compound obj based on
+/// either the chemical name or the pubchem id of the chemical
+pub enum ChemicalIdentifier {
+ /// The PubChem ID of the component.
+ PubchemID(u32),
+ /// The actual name of the component.
+ CompoundName(String),
+}
+
+
+#[allow(dead_code)]
+/// A struct to store information regarding the chemical properties of a
+/// particular substance. The "Chemical" struct is a wrapper for the
+/// pubchem::Compound object
+pub struct Chemical {
+ /// The (PubChem)[] CID of a compound.
+ pub pubchem_obj: pubchem::Compound,
+ /// Physical properties of a compound.
+ pub properties: ChemicalProperties,
+}
+
+#[allow(dead_code)]
+/// Implementation of the chemical of interest.
+impl Chemical {
+ /// Constructs a new chemical.
+ pub fn new(identifier: ChemicalIdentifier) -> Result {
+ let pubchem_chemical_object = match identifier {
+ ChemicalIdentifier::PubchemID(id) => pubchem::Compound::new(id),
+ ChemicalIdentifier::CompoundName(name) => pubchem::Compound::with_name(name.as_str()),
+ };
+ let mut request_counter = 0;
+ let mut cid_vec = None;
+ while request_counter <= 10 {
+ match pubchem_chemical_object.cids(){
+ Ok(cid_list) => {
+ cid_vec = Some(cid_list);
+ break;
+ },
+ _ => {
+ request_counter += 1;
+ thread::sleep(Duration::from_secs(10));
+ }
+ };
+ }
+
+ // let cid_vec = pubchem_chemical_object.cids().unwrap();
+ let cid: i32 = cid_vec.unwrap()[0];
+ let prop = ChemicalProperties::new(cid).unwrap();
+ Ok(Chemical {
+ pubchem_obj: pubchem_chemical_object,
+ properties: prop,
+ })
+ }
+ /// Returns the pubchem object for the compound.
+ pub fn get_pubchem_obj(&self) -> &pubchem::Compound {
+ &self.pubchem_obj
+ }
+
+ /// Returns the "ChemicalProperties" object for the "Chemical" object.
+ pub fn get_properties(&self) -> &ChemicalProperties {
+ &self.properties
+ }
+}
+
+#[allow(dead_code)]
+/// Struct containing properties of a chemical
+pub struct ChemicalProperties {
+ /// Pure species properties
+ pub critical: Option,
+
+ /// Heat capacity coefficients (optional, stored as an array)
+ pub heat_capacity: Option,
+
+ /// Transport properties (optional, could include viscosity, etc.)
+ pub transport: Option,
+
+ /// Additional chemical property categories
+ // Here we might add properties related to binary interactions, etc...
+ pub other_properties: Option>,
+}
+
+/// Trait to group all property libraries
+trait PropertyLibrary {
+ /// default function for connecting the database to pull relevant property information
+ fn oscps_db_connection(&self) -> &db_connection;
+}
+
+
+
+#[cfg(test)]
+mod chemical_species_tests {
+}
diff --git a/oscps-lib/src/properties/pure_species_properties.rs b/oscps-lib/src/properties/pure_species_properties.rs
new file mode 100644
index 0000000..f9b4983
--- /dev/null
+++ b/oscps-lib/src/properties/pure_species_properties.rs
@@ -0,0 +1,32 @@
+///# Pure Species Properties
+/// This sub-module will contain the information regarding the cheracteristic properties of pure
+/// species. This would include the molar mass (molecular weight), accentric factor, critical
+/// temperature, critical pressure, critical compressibility, critical molar volume, and normal
+/// boiling point.
+
+#[warn(unused_imports)]
+use crate::properties::*;
+use uom::si::f64;
+use std::sync::Arc;
+// use oscps_db;
+
+///#PureSpeciesProperties
+///
+///This will contain all the important properties for pure species
+pub struct PureSpeciesProperties {
+ pub species_obj_id: Arc,
+ pub antoine_equation_constants: Vec,
+ pub critical_temperature: f64::ThermodynamicTemperature,
+ pub molar_mass: f64::MolarMass,
+ pub normal_boiling_point: f64::ThermodynamicTemperature,
+ pub critical_molar_volume: f64::Volume,
+ pub accentric_factor: f64::Ratio,
+ pub compressibility_factor: f64::Ratio
+}
+
+///Functions to pull pure species properties from the database
+ // Database will need to handle API calls to external sources for information currently in the
+ // database.
+impl PureSpeciesProperties {
+
+}
diff --git a/oscps-lib/src/simulation.rs b/oscps-lib/src/simulation.rs
new file mode 100644
index 0000000..f10c491
--- /dev/null
+++ b/oscps-lib/src/simulation.rs
@@ -0,0 +1,170 @@
+//! # Simulation
+//!
+//! Allows for the construction of a simulation object. TODO: Implement this.
+
+use crate::blocks::{Block, Mixer};
+use crate::stream::Stream;
+// use std::collections::HashMap;
+use std::collections::BTreeMap;
+use std::sync::{Arc, RwLock};
+/// An Arc, RwLock, Box reference for threadsafe Block interactions.
+pub type BlockReference = Arc>>;
+/// An Arc, RwLock, Box reference for threadsafe Stream interactions.
+pub type StreamReference = Arc>>;
+
+/// Used to tell functions what type of block to add.
+pub enum BlockType {
+ /// Mix multiple streams into a single output stream.
+ Mixer,
+ /// Source for streams.
+ Source,
+ /// Sink for streams.
+ Sink,
+}
+
+// fn compute_outlet_phase_fractions(&self) {
+
+// }
+
+// fn compute_outlet_temperature(&self) {
+
+// }
+
+// fn compute_outlet_pressure(&self) {
+
+// }
+
+/// A struct for storing settings of the simulation
+#[derive(Debug, Clone, Default)]
+pub struct Settings {
+ // Add fields as needed
+}
+
+/// A struct for storing the current state of the simulation
+#[derive(Debug, Clone, Default)]
+pub struct SimulationState {
+ // Add fields as needed
+}
+
+impl SimulationState {
+ /// Create a new SimulationState.
+ pub fn new() -> Self {
+ return SimulationState {};
+ }
+}
+
+/// An enum used to represent errors.
+#[derive(Debug)]
+pub enum Err {
+ /// Error when a block is not found
+ BlockNotFound,
+ /// Error when a connector is not found
+ ConnectorNotFound,
+ /// Error when a block with a matching ID is already in the simulation
+ BlockExists,
+ /// Error when a connector with a matching ID is already in the simulation
+ ConnectorExists,
+ /// Any other error
+ Other(String),
+}
+
+/// The Simulation struct stores information pertaining to blocks and streams
+#[derive(Default)]
+#[allow(dead_code)]
+pub struct Simulation {
+ /// Stores all the blocks in the simulation
+ blocks: BTreeMap,
+ /// Stores all the streams in the simulation
+ streams: BTreeMap,
+ /// Stores simulation settings
+ settings: Settings,
+ /// Stores the state of the simlation
+ state: SimulationState,
+}
+
+impl Simulation {
+ /// Create a new simulation
+ pub fn new(settings: Settings) -> Self {
+ Self {
+ blocks: BTreeMap::new(),
+ streams: BTreeMap::new(),
+ settings,
+ state: SimulationState::new(),
+ }
+ }
+
+ /// Adds a block to the simulation and returns the ID of
+ /// the block.
+ #[allow(dead_code)]
+ pub fn add_block(&mut self, block: BlockType) -> u64 {
+ // Start with a block id of 1.
+ let mut id = 1;
+ while self.blocks.contains_key(&id) {
+ id += 1;
+ }
+
+ match block {
+ BlockType::Mixer => {
+ self.blocks
+ .insert(id, Arc::new(RwLock::new(Box::new(Mixer::new()))));
+ }
+ BlockType::Source => {
+ todo!()
+ }
+ BlockType::Sink => {
+ todo!()
+ }
+ }
+
+ return id;
+ }
+
+ /// Adds a stream to the simulation and returns the ID of
+ /// the stream.
+ #[allow(dead_code)]
+ pub fn add_stream(&mut self, from: BlockReference, to: BlockReference) -> u64 {
+ // Start with a stream ID of 1.
+ let mut id = 1;
+ while self.streams.contains_key(&id) {
+ id += 1;
+ }
+ self.streams
+ .insert(id, Arc::new(RwLock::new(Box::new(Stream::new(from, to)))));
+ return id;
+ }
+
+ // /// Add a block to the simulation.
+ // fn add_block(
+ // &mut self,
+ // block_id: u64,
+ // block: Arc>>,
+ // ) -> Result<(), Err> {
+ // if self.blocks.contains_key(&block_id) {
+ // return Err(Err::BlockExists);
+ // }
+ // self.blocks.insert(block_id, block);
+ // Ok(())
+ // }
+
+ // pub fn add_connector(&mut self, connector_id: u64, connector: Connector) -> Result<(), Err> {
+ // if self.connectors.contains_key(&connector_id) {
+ // return Err(Err::ConnectorExists);
+ // }
+ // self.connectors.insert(connector_id, connector);
+ // Ok(())
+ // }
+
+ // pub fn remove_block(&mut self, block_id: u64) -> Result<(), Err> {
+ // if self.blocks.remove(&block_id).is_none() {
+ // return Err(Err::BlockNotFound);
+ // }
+ // Ok(())
+ // }
+
+ // pub fn remove_connector(&mut self, connector_id: u64) -> Result<(), Err> {
+ // if self.connectors.remove(&connector_id).is_none() {
+ // return Err(Err::ConnectorNotFound);
+ // }
+ // Ok(())
+ // }
+}
diff --git a/oscps-lib/src/stream.rs b/oscps-lib/src/stream.rs
new file mode 100644
index 0000000..e5ceaf5
--- /dev/null
+++ b/oscps-lib/src/stream.rs
@@ -0,0 +1,24 @@
+//! # Stream
+
+// NOTE: Temporarily disabled until the thermodynamics crate is thread-safe.
+// use crate::thermodynamics::ThermoState;
+use crate::simulation::BlockReference;
+
+/// # Stream
+///
+/// Struct to hold stream information
+pub struct Stream {
+ /// Instance of ThermoState struct that holds thermodynamic information.
+ // pub thermo: Option, // HACK: Temporarily disable to enable thread-safety.
+ /// ID of source block
+ pub from: BlockReference,
+ /// ID of destination block
+ pub to: BlockReference,
+}
+
+impl Stream {
+ /// Constructor for 'Stream' struct
+ pub fn new(from: BlockReference, to: BlockReference) -> Stream {
+ Stream { from, to }
+ }
+}
diff --git a/oscps-lib/src/thermodynamics.rs b/oscps-lib/src/thermodynamics.rs
new file mode 100644
index 0000000..dce49bc
--- /dev/null
+++ b/oscps-lib/src/thermodynamics.rs
@@ -0,0 +1,95 @@
+//! # Thermodynamics
+//!
+//! This module will hold all the functions related to calculating
+//! themrodynamic properties for the blocks and chemical species.
+
+///Importing EOSModels
+pub mod eos_models;
+
+/// Importing chemical properties
+use crate::properties::Chemical;
+
+///Importing External Packages
+use uom::si::f64::*;
+use uom::si::mass;
+use uom::si::pressure;
+use uom::si::thermodynamic_temperature;
+use uom::si::energy;
+use uom::si::amount_of_substance;
+
+#[allow(dead_code)]
+/// #ThermodynamicConstants
+///
+/// Struct for storing physical constants for thermodynamics.
+/// TODO: Reimplement the use of uom for dimensional analysis.
+pub enum ThermodynamicConstants {
+ /// The Universal gas constant in J/(mol*K)
+ UniversalGasConstant, // J/(mol*K)
+ /// Standard temperature in K
+ StandardTemperature, // T_0
+ /// Standard pressure in Pa
+ StandardPressure, // P_0
+ /// Avogadro's number in mol^-1
+ AvogadroNumber, // N_A
+}
+
+#[allow(dead_code)]
+/// Implements values of thermodynamic constants.
+impl ThermodynamicConstants {
+ /// Returns the value of the thermodynamic constant with its appropriate type.
+ pub fn value(&self) -> Box {
+ match self {
+ ThermodynamicConstants::UniversalGasConstant => {
+ let r = 8.314462618;
+ let constant = Energy::new::(r) / (ThermodynamicTemperature::new::(1.0)* AmountOfSubstance::new::(1.0));
+ Box::new(constant)
+ },
+ ThermodynamicConstants::StandardTemperature => {
+ Box::new(ThermodynamicTemperature::new::(273.15))
+ }
+ ThermodynamicConstants::StandardPressure => {
+ Box::new(Pressure::new::(101_325.0))
+ },
+ ThermodynamicConstants::AvogadroNumber => Box::new(6.02214076e23), //Units: particles/mole
+ }
+ }
+}
+
+///Thermodynamic Packages.
+///
+///#MaxwellRelations
+///
+///Will be a common trait for all the thermodynamic packages and will include common functions.
+///Will also enable to user to switch between thermodynamic packages within the StreamThermoState struct
+///
+///The thermodynamic pacakges can be used by the blocks for any relevant calculations
+///
+///For calculations, the thermodynamic packages will call upon the property struct for relevant
+///info.
+///
+///TODO: Currently the rust std::autodiff is still experimental. Need to wait for this release. In
+///the meantime, we will either manually write out the derivatives or use a third party autdiff
+///package (the third party is: https://crates.io/crates/autodiff)
+pub trait MaxwellRelations{
+ ///Calculating the Enthalpy
+ fn enthalpy(&self) -> MolarEnergy;
+ ///Calculating the Entropy
+ fn entropy(&self) -> MolarHeatCapacity;
+ ///Calculate pressure
+ fn pressure(&self) -> Pressure;
+ ///Calculate volume
+ fn volume(&self) -> Volume;
+ ///Calculate temperature
+ fn temperature(&self) -> ThermodynamicTemperature;
+ ///Calculate vapor fractions
+ fn vapor_fraction(&self) -> Ratio;
+ ///Calculate heat capacity
+ fn heat_capacity_const_pressure(&self) -> MolarHeatCapacity;
+ ///Calculate internal temperature
+ fn internal_energy(&self) -> MolarEnergy;
+ ///Calculate gibbs free energy
+ fn gibbs_free_energy(&self) -> Energy;
+}
+
+#[cfg(test)]
+mod thermo_tests {}
diff --git a/oscps-lib/src/thermodynamics/eos_models.rs b/oscps-lib/src/thermodynamics/eos_models.rs
new file mode 100644
index 0000000..c9fcd38
--- /dev/null
+++ b/oscps-lib/src/thermodynamics/eos_models.rs
@@ -0,0 +1,23 @@
+///#EOSModel
+///
+///This struct will represent the different thermodynamic equations of state
+
+
+use crate::thermodynamics::*;
+use std::sync::Arc;
+use uom::si::f64::*;
+use uom::si::molar_energy;
+use uom::si::molar_heat_capacity;
+use uom::si::pressure;
+use uom::si::thermodynamic_temperature;
+use uom::si::energy;
+use uom::si::amount_of_substance;
+use uom::si::volume;
+use uom::si::ratio;
+
+
+/// This struct will hold the chemical potential equation for each type of equation of state
+/// Inspired by: https://github.com/ClapeyronThermo/Clapeyron.jl
+pub struct EOSModel {
+
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..de49555
--- /dev/null
+++ b/package.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": { "@tauri-apps/api": "^1.6.0" },
+ "devDependencies": {
+ "@sveltejs/adapter-static": "^3.0.2",
+ "@tauri-apps/cli": "^1.6.0"
+ }
+}
\ No newline at end of file
diff --git a/q.log b/q.log
new file mode 100644
index 0000000..4efa4ce
--- /dev/null
+++ b/q.log
@@ -0,0 +1,60 @@
+This is LuaHBTeX, Version 1.22.0 (TeX Live 2025) (format=lualatex 2025.4.27) 10 MAY 2025 16:48
+ restricted system commands enabled.
+**q
+(c:/texlive/2025/texmf-dist/tex/latex/tools/q.tex
+LaTeX2e <2024-11-01> patch level 2
+L3 programming layer <2025-03-26>
+Lua module: luaotfload 2024-12-03 v3.29 Lua based OpenType font support
+Lua module: lualibs 2023-07-13 v2.76 ConTeXt Lua standard libraries.
+Lua module: lualibs-extended 2023-07-13 v2.76 ConTeXt Lua libraries -- extended
+collection.
+luaotfload | conf : Root cache directory is "C:/texlive/2025/texmf-var/luatex-ca
+che/generic/names".
+luaotfload | init : Loading fontloader "fontloader-2023-12-28.lua" from kpse-res
+olved path "c:/texlive/2025/texmf-dist/tex/luatex/luaotfload/fontloader-2023-12-
+28.lua".
+Lua-only attribute luaotfload@noligature = 1
+luaotfload | init : Context OpenType loader version 3.134
+Inserting `luaotfload.node_processor' in `pre_linebreak_filter'.
+Inserting `luaotfload.node_processor' in `hpack_filter'.
+Inserting `luaotfload.glyph_stream' in `glyph_stream_provider'.
+Inserting `luaotfload.define_font' in `define_font'.
+Lua-only attribute luaotfload_color_attribute = 2
+luaotfload | conf : Root cache directory is "C:/texlive/2025/texmf-var/luatex-ca
+che/generic/names".
+Inserting `luaotfload.harf.strip_prefix' in `find_opentype_file'.
+Inserting `luaotfload.harf.strip_prefix' in `find_truetype_file'.
+Removing `luaotfload.glyph_stream' from `glyph_stream_provider'.
+Inserting `luaotfload.harf.glyphstream' in `glyph_stream_provider'.
+Inserting `luaotfload.harf.finalize_vlist' in `post_linebreak_filter'.
+Inserting `luaotfload.harf.finalize_hlist' in `hpack_filter'.
+Inserting `luaotfload.cleanup_files' in `wrapup_run'.
+Inserting `luaotfload.harf.finalize_unicode' in `finish_pdffile'.
+Inserting `luaotfload.glyphinfo' in `glyph_info'.
+Lua-only attribute luaotfload.letterspace_done = 3
+Inserting `luaotfload.aux.set_sscale_dimens' in `luaotfload.patch_font'.
+Inserting `luaotfload.aux.set_font_index' in `luaotfload.patch_font'.
+Inserting `luaotfload.aux.patch_cambria_domh' in `luaotfload.patch_font'.
+Inserting `luaotfload.aux.fixup_fontdata' in `luaotfload.patch_font_unsafe'.
+Inserting `luaotfload.aux.set_capheight' in `luaotfload.patch_font'.
+Inserting `luaotfload.aux.set_xheight' in `luaotfload.patch_font'.
+Inserting `luaotfload.rewrite_fontname' in `luaotfload.patch_font'.
+Inserting `tracingstacklevels' in `input_level_string'. File ignored
+)
+! Emergency stop.
+<*> q
+
+*** (job aborted, no legal \end found)
+
+
+
+Here is how much of LuaTeX's memory you used:
+ 16 strings out of 471941
+ 100000,662416 words of node,token memory allocated 301 words of node memory still in use:
+ 1 hlist, 1 dir, 3 kern, 1 glyph, 1 attribute, 39 glue_spec, 1 attribute_list
+nodes
+ avail lists: 2:10,3:3,4:1
+ 26706 multiletter control sequences out of 65536+600000
+ 14 fonts using 591679 bytes
+ 12i,0n,13p,80b,15s stack positions out of 10000i,1000n,20000p,200000b,200000s
+! ==> Fatal error occurred, no output PDF file produced!
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index e7a11a9..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- println!("Hello, world!");
-}