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) + +![Rust Tests](https://github.com/OSCPS-Project/OSCPS/actions/workflows/rust-tests.yml/badge.svg?branch=develop) +![Documentation](https://github.com/OSCPS-Project/OSCPS/actions/workflows/check-docs.yml/badge.svg?branch=develop) + +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!"); -}