๐Ÿโฉ๐ŸŽ SFPPy - GUI ๐Ÿ’ซ
GitHub SFPPy
v1.37 ๐Ÿ“ฉ

 

๐Ÿ’ซ SFPPy Graphical User Interface (GUI)

Synopsis

This concise guide explains how to simulate mass transfer for chemical safety assessments of plastic materials in food contact ๐Ÿฝ๏ธ. It introduces interactive Widgets๐ŸŽš๏ธ in a Jupyter notebook that rely on the SFPPy library. The interface works both locally (in a standard web browser) and remotely on platforms such as Google Colab. Thanks to this hybrid approach, you can seamlessly combine Python code and a Graphical User Interface in one flexible environment.

 

Tip

You can initially skip to specific sections, but revisiting the introduction is crucial for designing scenarios involving repeated use, non-food contact simulations, complex packaging, multi-step processes, or sensitivity analyses.

 


0 | Introduction

0.1 | Preamble

Why SFPPy - GUI ๐Ÿ’ซ is running within a Jupyter/Colab notebook?

 

Why does SFPPy - GUI ๐Ÿ’ซ run inside a Jupyter or Colab notebook? Simply put, the rise of AI drove a more flexible design approach, rather than the traditional client-server GUI used in SFPP3. SFPPy provides a modular collection of tools and methods capable of handling complex, chained simulationsโ€”much like FMECAengine but in a more streamlined manner and and without the cost or constraints of a MATLAB license. Once it was ported to Jupyter and Colab (both HTML-based environments), it became evident that we could restore GUI-like features via widgets. A widge ๐ŸŽš๏ธ is an interactive interface component that can execute predefined actions and share data with the notebook. In essence, gui.ipynb (stored in the notebookโ€™s main folder) serves as the graphical interface for SFPPy, exposing its key functionalities with zero coding required.

 

0.2 | Widgets

Each core SFPPy module in ๐Ÿ“‚Patankar\ folder, except properties, will expose one or several widgets required for the steps of โ€œmigration modelingโ€.

You will read in ๐Ÿ“„gui.ipynb the necessary components, which are imported at the beginning of the notebook:

In SFPPy, data ๐„œ and simulation models ๐Ÿ”ข follow an intuitive syntactic structure ๐Ÿ”ก that AI ๐Ÿง  can process:

Here, mymigration, mysubstances, mycontacts, mypackaging, mymaterials are containers (Python dict types) storing the result with a key ; "mig1" (default migration kinetics name), โ€œm1โ€ (default substance name), โ€œcontact1โ€ (default contact/food/simulant name), "shape1" (default packaging geometry in 3D), โ€œmultilayer1โ€ (default multilayer/monolayer structure).

All these objects have abstract representation in SFPPy and you do need to define them completely. SFPPy will ask you some important information via the widgets and will infer the missing properties using its own internal rules (prediction properties), its own databases (contact conditions, geometries), the chemical information found on PubChem, etc.


A โ€œmigrationโ€ workflow can be visualized as a pipelineโ€”moving a substance from left to right to represent the physical process of mass transfer. Associated information also follows this left-to-right path, except for the packaging geometry, which affects both the food content and the packaging material. SFPPy defines three operators: % (substance โŒฌ injection), << (inheritance) and >> (propagate or run).

m% F << G >> P >> S >> S
substance widget
container: mysybstances
key: mig1
food widget
container: mycontacts
key: contact1
packaging widget
container: mypackaging
key: shape1
layer widget
container: mymaterials
key: multilayer1
migration widget
container: mymigration
key: mig1
โŒฌ ๐Ÿฅ—๐Ÿฅช๐ŸŸ๐Ÿฅ˜๐Ÿณ๐Ÿ”ฅโ˜€๏ธโ„๏ธโฑ๏ธ ๐Ÿฅซ๐ŸŒฏ๐Ÿงƒ๐Ÿซ™๐Ÿผ๐Ÿฑ โ™ณโ™ดโ™ตโ™ถโ™ทโ™ธโ™นโ™ผโ™ฝ ๐Ÿ“ˆ๐Ÿ“Š๐Ÿ“‰

 

You can launch widgets ๐ŸŽš๏ธ in any order, provided that the final combination of operations is coherent. If one widgetโ€™s results inform another, they simply โ€œplug inโ€ via connectors ๐Ÿ”Œ. By substituting or chaining objects โซ˜โซ˜โซ˜, you can examine many different configurations.

 

Note

For ease of use, the food type, packaging shape, polymer(s), and substance are preselected from default values or a saved session when the notebook opens. This triggers an immediate simulation and displays the results without user interaction. Although convenient, it may be undesirable in certain production environments. To change this, look for the final line of the first cell:

 


1 | Food Widget: ๐ŸŽš๏ธF

In SFPPy, โ€œfoodโ€ and โ€œcontactโ€ are handled by classes that capture different physical conditions (presence or absence of food), diverse food types (fatty/aqueous, textural properties, simulants), packaging storage conditions (stacked, rolled, temperature, duration), as well as food storage and processing (frozen, chilled, ambient, hot, pasteurization, frying, etc.). Internally, ๐ŸŽš๏ธF inherits several classes, and the Food Widget ๐ŸŽš๏ธ merges up to four levels of options.

 

Screenshot-20250318211900-2600x912


Screenshot-20250318213106-1300x306Screenshot-20250318213343-1244x480

 

Tip

The Food Widget๐ŸŽš๏ธ configures:

type of contact polarity/chemical nature mass transfer coefficient contact time contact temperature

You can inspect contact time and temperature with:

SFPPy treats food as a 0D entity, with its volume and contact surface area determined by the Packaging Widget. A Robin (third-kind) boundary condition governs mass transfer using the partition coefficient and mass transfer coefficient. By default, SFPPy automatically assigns both values, drawing on the foodโ€™s texture and the migrantโ€™s chemical nature, along with the composition of the food or simulant. The Henry-like coefficient k0 is derived from models in patankar.property โ€”typically a Flory-Huggins approach. In SFPPy - GUI๐Ÿ’ซ, these selections are done automatically but can be overridden in custom cells.

 

 


2 | Packaging Widget: ๐ŸŽš๏ธG

The representation of the packaging is implicit and a database of basic geometries is proposed to extract the surface area in contact with F and the internal volume.

 

Screenshot-20250318223812-1770x1238


Note

As shown, the Packaging Widget uses a preset library of basic geometries to derive surface area and internal volume. Although the underlying geometry class can handle connected shapes, the widget itself is more constrained. For instance, the bottle geometry consists of two cylinders. While the widgetโ€™s interface uses only SI units, SFPPy internally supports both SI and imperial units (and their variations).

 


 

Screenshot-20250318223946-1560x842


Tip

The Packaging Widget sets two key attributes:

surfacearea volume

They can be reviewed with:

The geometry automatically informs F (food) and eventually P (material) using the pipeline F<< G>>P. This transfer includes contact temperature and duration parameters.

 

 


3 | Layer Widget: ๐ŸŽš๏ธP

The Layer Widget ๐ŸŽš๏ธ lets you assemble up to 10 layers (e.g., polymer, adhesive, paperboard, air) with assigned thicknesses and initial substance concentrations. The first layer in the user interface (index 1) contacts F, which is index 0. Concentration units are arbitrary (a.u.), carrying directly into F as well.


Screenshot-20250318232029-2428x1772


Note

The polymer database powering the widget includes density vs. temperature, glass transition temperature, crystallinity, melting point, and monomer composition, all automatically attached to the layer object when selected.


Screenshot-20250318232216-1322x702


Tip

Each layer (1 through 10) is defined by:

material/polymer thickness initial concentration

You can confirm with:

Layer objects support various attributes and methods for calculating diffusivity D and Henry-like coefficients k at a specified temperature. Layers can be combined with the + operator. Although the Layer Widget does not allow you to override D or k values, you can add a dedicated cell:

To set diffusivities (or Henry coefficients) in a more flexible manner, SFPPy provides layerLink objects:

 

 


 

4 | Substance Widget : ๐ŸŽš๏ธm

The Substance Widget๐ŸŽš๏ธ lets you run mass-transfer simulations for thousandsโ€”if not millionsโ€”of chemicals, provided they exist on PubChem and are recognized as pure compounds. Selection is a two-step process. You can enter chemical names, synonyms, or CAS numbers; SFPPy first checks its local cache, then queries the internet (if needed).

 

The search box accepts indifferently chemical, trade names and CAS numbers. The search will be carried out in the cached files of SFPPy and then over internet.

Screenshot-20250319230923-2064x296

 


 

Once the Substance Widget finds a match, the key information appears in the right panel and a structural sketch on the left. Clicking the green button instantiates the substance in the notebook under the default name โ€œm1โ€.

Screenshot-20250319231222-2572x1118

 


Note

The right panel features additional details for verification, including a โ€œNameโ€ dropdown with synonyms. The legal status under Regulation (EU) No 10/2011 (for plastic FCM) is highlighted in blue alongside an EU logo.

 


 

gnome-shell-screenshot-eity9dgnome-shell-screenshot-askf7x


 

Note

For substances not positively listed, an automated ToxTree screening is displayed in red, in contrast to the EU-authorized compounds shown in blue. This toxicological assessment relies on a private ToxTree installation bundled in the SFPPy distribution, also functional in Google Colab. Clicking on the CID name opens the PubChem page with more legal or toxicological details.

 


 

gnome-shell-screenshot-3df1dy

 


 

Tip

The SFPPy - GUI ๐Ÿ’ซ pipeline favors real substances (PubChem-validated) over purely numeric or hypothetical inputs. Once you instantiate a substance, SFPPy automatically retrieves its chemical structure and molecular descriptors, used to compute diffusivity D and Henry-like coefficient k from multiple molecular models in patankar.property.

chemical structure molecular descriptors calculated properties acceptable limits (SML)

 

Possible model candidates are filtered by a rule-based system, and you can always integrate new models or rules if needed.

 

 


 

5 | Simulation Widget: ๐ŸŽš๏ธS

 

In SFPPy - GUI ๐Ÿ’ซ, the simulation is executed by the pipeline:

On-screen, the simulation Widget ๐ŸŽš๏ธ shows you four dropdown menus for picking the substance, contact conditions, packaging, and material. Results go into the global variable mymigration; you can rename the simulation (default "mig1").

Screenshot-20250320000611-2114x498


Note

You may overwrite previously saved conditions by instantiating them under the same name. Refer to the introduction if you want to make programmatic updates.

 


Tip

Itโ€™s straightforward to run multiple substances (e.g., 10 or more) by picking each from the widget. For more intricate scenarios, define them programmaticallyโ€”for instance:

This sequence โ€œmig123โ€ combines three distinct contact conditions. SFPPyโ€™s documentation and example notebooks detail how to merge or aggregate these results.

 


6 | Result analysis ๐ŸŽš๏ธ๐Ÿ“Š

The Result Analysis Widget ๐ŸŽš๏ธ can be placed in any notebook to process results stored in the global dictionary mymigration (the default location for SFPPy - GUI ๐Ÿ’ซ.

 

gnome-shell-screenshot-danw2a

 


 

Note

With this widget, you can visualize desorption or migration kinetics (CF vs. t), overlay SML values, and inspect concentration profiles (Cx,t vs. x) at multiple time points. By default, SFPPy simulates for twice the specified time, so you can more easily observe long-term behavior.

 

 

The results can be tabulated for particular times and exported simultaneously as CSV, XLS, PDF and PNG.


gnome-shell-screenshot-1v0u28

 

Tip

Each Python object in SFPPy has methods for merging, aggregation, and export, making the ecosystem highly adaptable.

Also remember to save your notebook to preserve widget values.

 

Screenshot-20250320075331-1060x480

 


 

7 | Placing the widgets ๐ŸŽš๏ธ where ever you want

 

7.1 | Reusing SFPPy widgets

 

You can place SFPPy widgets ๐ŸŽš๏ธ wherever you see fit in the notebook. Only the Simulation and Result widgets rely on the outputs of the others, so they typically go last.

 

Once you have SFPPy available, simply import the necessary modules and set up the environment in the first cell.

After confirming you see the SFPPy logo,

Screenshot-20250320065907-1692x150


you can instantiate a widgetโ€”for example, the Substance Widget ๐ŸŽš๏ธ.


Note

In Google Colab, you can rapidly create forms via Forms in Colab, though these arenโ€™t portable outside Colab without IPyform, which is not included in SFPPy. On the other hand, Jupyter widgets work seamlessly in both environments and form the backbone of SFPPy - GUI ๐Ÿ’ซ.

 


 

7.2 | Develop your own Widgets ๐ŸŽš๏ธ for SFPPy

Tip

A simple way to build custom widgets is to use ipywidgets.interact (built into Jupyter and Colab). This method inspects function arguments (scalars, lists, strings) and automatically renders a form. Updating a parameter triggers the function in real time.

 

A minimalist search box for extracting toxicological and molecular data could be:

Minimalist Search Box

The snippet above demonstrates a minimalist approach: changing the text box triggers a PubChem + ToxTree lookup and displays the molecule. Though it works, there is no error handling for unrecognized names.
Substance Search Interface

 


Here, we add dynamic sizing for the molecule image and store the newly created substance under โ€œm2โ€ for subsequent simulations. Real-time resizing is supported through the IntSlider widget.

 

Tip

The new substance so-called m2 is added to the collection of substances in the notebook via mysubstances["m2"] = tox

.
Optimized Search Box

The placement of the objects is improved and we can zoom/dezoom in real time on the substance within the range 10-120%.
The new substance "m2" is automatically to the definition of mysubstances and can be used for simulation.

Screenshot-20250320120510-788x210
Substance Search Interface

 


7.3 | An Advanced Example

 

7.3.1 | Connecting simulation parameters and Widgets ๐ŸŽš๏ธ

All parameters used in simulations can be tied to widget controls. To make this possible, SFPPy provides a layerLink mechanism, which links internal simulation parameters to external widget values without needing to manually reassign them each time. These links broaden the capability of the core operators %, <<, and >>:

Once a parameter is โ€œlinked,โ€ any change to the widget automatically updates the simulation objects.

 

Note

layerLink objects can be attached to diffusivities D, Henry-like coefficients k (including partition coefficients), initial concentrations C0, layer thicknesses l, and temperature T. These all reside in layer objects, themselves part of the global container mymaterials.

 

Below is a minimal demonstration. We have a material named mymaterials["test"] whose first layerโ€™s D and k values are driven by layerLink objects. The simulation pipeline is:

The line below binds D in the first layer (index 0) to a layerLink object:

From this point forward, assigning a new value to D[0] immediately reflects in the simulationโ€”no need to call "test" again. An equivalent process applies to k via mymaterials["test"].klink.

 

Tip

Unlinking is as simple as mymaterials["test"].Dlink = None, restoring the original diffusion data or model.

You can target only selected layers by setting, for example, D[2] = 2e-12 for layer index 2, or by creating a layerLink with indices=[0,2], values=[1e-12,2e-12].

 

 


 

7.3.2 A Full Example

In this advanced example, we create a new material "test" (copied from "multilayer1") and dynamically link D and k for the first layer (index 0). We then expose these parameters via sliders using ipywidgets.interact. When a slider moves, SFPPy automatically reruns the simulation with updated D[0] and k[0]. The resultโ€”named โ€œtestโ€โ€”appears under โ€œmymigration" and can be plotted or further analyzed.

 

.
Interface to explore the effects of D[0] and k[0]

Since the values span over several decades, a decimal log scale is proposed.
The simulated curve is updated in real time once the slider is released.

The result "test" is automatically updated in the global variable mymigration and is accessible to the plotmigration Widget๐ŸŽš๏ธ.

Screenshot-20250320130610-822x432
Interface to explore the effects of D[0] and k[0]

 

Important

In summary, layerLink is a quick way to connect any material parameter to a live user interface. Changing the slider drives real-time updates of diffusion and partition coefficients (or other properties), making it straightforward to investigate โ€œwhat-ifโ€ scenarios on the fly.

 


8 | Final Words

Note

This tutorial does not cover combining results from multiple simulations in CFSimulationContainer or merging outcomes from chained simulations (using the + operator). See examples (1, 2, 3) for that. Likewise, layerLink-based fitting to experimental data appears in example 4.

For parameters that do not change dynamically during simulation, use the update() method on F (`food/foodphysics objects) or P (layer objects). For instance:

SFPPy interprets omitted units as SI (meters, seconds). It also supports standard and non-standard unit labels (e.g., day, days, week, month, months, ยตm, um) to prevent confusion.

Important

Concentration units remain in [a.u.] due to the complexities of mass vs. volume concentrations. If you assume density does not affect partitioning, you may consistently use the same arbitrary units for the initial polymer concentrations and CF in food. SFPPy does feature an internal density model for many systems (polymers, liquids) if higher precision is needed.

Caution

Finally, note that any parameter you do not explicitly define is auto-assigned by SFPPy. The default SFPPy - GUI ๐Ÿ’ซ does not offer a direct compliance-reporting mechanism. For that purpose, refer to SFPPy - Comply โœ…, which is fully compatible and allows advanced compliance and reporting.

 


 

๐Ÿโฉ๐ŸŽ SFPPy for Food Contact Compliance and Risk Assessment
Contact Olivier Vitrac for questions | Website | Documentation

 

โš ๏ธ DISCLAIMER
This material is provided โ€œAS ISโ€ solely for demonstration and training. No warrantyโ€”express or impliedโ€”is given regarding its accuracy, completeness, or suitability for any particular purpose. ๐Ÿ“Œ Users are fully responsible for assessing its relevance and ensuring compliance with all applicable regulations. ๐Ÿ”ฌ The illustrative example underscores the risks of treating โ€œmigration modelingโ€ as a mere โ€œblack box,โ€ potentially leading to misinterpretation of mass transfer phenomena. ๐Ÿšซ Neither the authors nor their organizations accept any liability for reliance on or use of this material.

 


๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ
๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ
๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ
๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐ŸŽ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿ๐Ÿ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ
๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ
๐Ÿฝ๏ธ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐ŸŽ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ
๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ๐Ÿฝ๏ธ
GitHub SFPPy
v1.37 ๐Ÿ“ฉ