Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebcfeba1e4 | |||
| 22f0798f81 | |||
| 5b51c67069 | |||
| d9efaa4620 | |||
| cf55db126d | |||
|
|
8d81052d4c | ||
| 998228a604 |
647
.gitignore
vendored
647
.gitignore
vendored
@@ -1 +1,648 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[codz]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
# Pipfile.lock
|
||||||
|
|
||||||
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# uv.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
# poetry.lock
|
||||||
|
# poetry.toml
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||||
|
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||||
|
# pdm.lock
|
||||||
|
# pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# pixi
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||||
|
# pixi.lock
|
||||||
|
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||||
|
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||||
|
.pixi
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
*.rdb
|
||||||
|
*.aof
|
||||||
|
*.pid
|
||||||
|
|
||||||
|
# RabbitMQ
|
||||||
|
mnesia/
|
||||||
|
rabbitmq/
|
||||||
|
rabbitmq-data/
|
||||||
|
|
||||||
|
# ActiveMQ
|
||||||
|
activemq-data/
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.envrc
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
# .idea/
|
||||||
|
|
||||||
|
# Abstra
|
||||||
|
# Abstra is an AI-powered process automation framework.
|
||||||
|
# Ignore directories containing user credentials, local state, and settings.
|
||||||
|
# Learn more at https://abstra.io/docs
|
||||||
|
.abstra/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||||
|
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||||
|
# you could uncomment the following to ignore the entire vscode folder
|
||||||
|
# .vscode/
|
||||||
|
|
||||||
|
# Ruff stuff:
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
||||||
|
|
||||||
|
# Marimo
|
||||||
|
marimo/_static/
|
||||||
|
marimo/_lsp/
|
||||||
|
__marimo__/
|
||||||
|
|
||||||
|
# Streamlit
|
||||||
|
.streamlit/secrets.toml
|
||||||
|
|
||||||
|
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
*.env
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
|
||||||
|
[Dd]ebug/x64/
|
||||||
|
[Dd]ebugPublic/x64/
|
||||||
|
[Rr]elease/x64/
|
||||||
|
[Rr]eleases/x64/
|
||||||
|
bin/x64/
|
||||||
|
obj/x64/
|
||||||
|
|
||||||
|
[Dd]ebug/x86/
|
||||||
|
[Dd]ebugPublic/x86/
|
||||||
|
[Rr]elease/x86/
|
||||||
|
[Rr]eleases/x86/
|
||||||
|
bin/x86/
|
||||||
|
obj/x86/
|
||||||
|
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||||
|
bld/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Build results on 'Bin' directories
|
||||||
|
**/[Bb]in/*
|
||||||
|
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||||
|
# (https://github.com/github/gitignore/pull/3736)
|
||||||
|
#!**/[Bb]in/*.refresh
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
*.trx
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Approval Tests result files
|
||||||
|
*.received.*
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.idb
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||||
|
!Directory.Build.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
**/.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
**/.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
**/.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
**/__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
#tools/**
|
||||||
|
#!tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
MSBuild_Logs/
|
||||||
|
|
||||||
|
# AWS SAM Build and Temporary Artifacts folder
|
||||||
|
.aws-sam
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
**/.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
**/.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
**/.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,5 +1,6 @@
|
|||||||
|
# @ImportStructure ; ref@todo/main.py , Element 3
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, messagebox, filedialog
|
from tkinter import ttk, messagebox, filedialog, font
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import json
|
import json
|
||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
@@ -15,16 +16,27 @@ import queue
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
# Local Dependencies
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
from wiki import main as wiki
|
||||||
|
from pfand_scanner import launch_pfand_scanner
|
||||||
|
from updater import open_updater as open_updater, run_silent_update
|
||||||
|
from tgtg_orderchecker import main as tgtg
|
||||||
|
from tgtg_orderchecker import setupkey as tgtg_kt
|
||||||
|
from todo.main import todo_instance
|
||||||
|
except ImportError:
|
||||||
|
print("tried to import without package name. failed, trying to import using package name now")
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# @LocalImportStructure ; ref@todo/main.py , Element 3
|
||||||
from PfandApplication.wiki import main as wiki
|
from PfandApplication.wiki import main as wiki
|
||||||
from PfandApplication.pfand_scanner import launch_pfand_scanner
|
from PfandApplication.pfand_scanner import launch_pfand_scanner
|
||||||
from PfandApplication.updater import open_updater as open_updater, run_silent_update
|
from PfandApplication.updater import open_updater as open_updater, run_silent_update
|
||||||
from PfandApplication.tgtg_orderchecker import main as tgtg
|
from PfandApplication.tgtg_orderchecker import main as tgtg
|
||||||
from PfandApplication.tgtg_orderchecker import setupkey as tgtg_kt
|
from PfandApplication.tgtg_orderchecker import setupkey as tgtg_kt
|
||||||
from PfandApplication.todo import main as todo
|
|
||||||
from PfandApplication.todo.main import todo_instance
|
from PfandApplication.todo.main import todo_instance
|
||||||
|
|
||||||
|
|
||||||
class Achievement:
|
class Achievement:
|
||||||
def __init__(self, title, description, condition_type, condition_value):
|
def __init__(self, title, description, condition_type, condition_value):
|
||||||
self.title = title
|
self.title = title
|
||||||
@@ -519,32 +531,156 @@ class PfandCalculator:
|
|||||||
close_button.grid(row=2, column=1, padx=10, pady=10, sticky="ew")
|
close_button.grid(row=2, column=1, padx=10, pady=10, sticky="ew")
|
||||||
|
|
||||||
todo_button = (
|
todo_button = (
|
||||||
tk.Button( # Looks like fucking shit , fix this horsecrap in the future
|
tk.Button( # fixed this in commit 5b51c670694ba813a56880eabbdb8a8f6446fa7c
|
||||||
about_window, text="Load Todo", command=self.create_todo_list
|
about_window, text="Load Todo", command=self.create_todo_list
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
todo_button.grid(row=2, column=3, padx=10, pady=10, sticky="ew")
|
|
||||||
|
|
||||||
# Todo
|
todo_button.grid(row=3, column=0, columnspan=2, padx=10, pady=10, sticky="ew")
|
||||||
|
|
||||||
|
# ref@todo/main.py
|
||||||
def create_todo_list(self):
|
def create_todo_list(self):
|
||||||
todo = tk.Toplevel(self.root)
|
todo = tk.Toplevel(self.root)
|
||||||
todo.title("Todo Liste")
|
todo.title("Todo Liste")
|
||||||
todo.resizable(True, True)
|
todo.resizable(True, True)
|
||||||
|
|
||||||
|
segoeui = font.Font(family="Segoe UI",size=11,weight="normal")
|
||||||
|
monaco = font.Font(family="Monaco", size=9, slant="italic")
|
||||||
|
|
||||||
|
seperator = ttk.Separator(todo, orient="horizontal")
|
||||||
|
seperator2 = ttk.Separator(todo, orient="horizontal")
|
||||||
|
|
||||||
|
|
||||||
for col in range(2):
|
for col in range(2):
|
||||||
todo.grid_columnconfigure(col, weight=1)
|
todo.grid_columnconfigure(col, weight=1)
|
||||||
todo.grid_rowconfigure(0, weight=0)
|
todo.grid_rowconfigure(0, weight=0)
|
||||||
|
|
||||||
|
def close_todo():
|
||||||
|
todo.destroy()
|
||||||
|
|
||||||
|
# Function to get done Todo Elements
|
||||||
|
# tuple[0] total, [1] done. [2] precent for pbar
|
||||||
|
def return_elements(todo_string : str) -> tuple[int, int, int]:
|
||||||
|
total = todo_string.count("[ ]") + todo_string.count("[x]")
|
||||||
|
done = todo_string.count("[x]")
|
||||||
|
percent_done = (done / total) * 100 if total > 0 else 0
|
||||||
|
return total, done, percent_done
|
||||||
|
|
||||||
|
progress_bar = ttk.Progressbar(todo, value=return_elements(todo_instance.load_todo())[2], style='Striped.Horizontal.TProgressbar')
|
||||||
|
|
||||||
# Shit will display here that is going to be loaded from external
|
# Shit will display here that is going to be loaded from external
|
||||||
label_todo = tk.Label(
|
label_todo = tk.Label( todo,
|
||||||
todo,
|
|
||||||
text=todo_instance.load_todo(),
|
text=todo_instance.load_todo(),
|
||||||
|
font=segoeui,
|
||||||
padx=10,
|
padx=10,
|
||||||
pady=10,
|
pady=10,
|
||||||
justify="center",
|
justify="left",
|
||||||
anchor="center",
|
anchor="center",
|
||||||
)
|
)
|
||||||
label_todo.grid(row=1, column=0, columnspan=2, pady=0, sticky="nsew")
|
|
||||||
|
label_todo_changelog = tk.Label( todo,
|
||||||
|
text=todo_instance.load_changelog(),
|
||||||
|
font=segoeui,
|
||||||
|
padx=10,
|
||||||
|
pady=10,
|
||||||
|
justify="left",
|
||||||
|
anchor="center"
|
||||||
|
)
|
||||||
|
|
||||||
|
label_todo_tldr = tk.Label( todo,
|
||||||
|
text=todo_instance.load_tldr(),
|
||||||
|
font=segoeui,
|
||||||
|
padx=10,
|
||||||
|
pady=10,
|
||||||
|
anchor="center",
|
||||||
|
justify="left"
|
||||||
|
)
|
||||||
|
|
||||||
|
percent = return_elements(todo_instance.load_todo())[2]
|
||||||
|
percent = round(percent, 2)
|
||||||
|
|
||||||
|
label_todo_percentage_bar_text = tk.Label(
|
||||||
|
todo,
|
||||||
|
text=f"{percent}% Done (For Todo)",
|
||||||
|
font=monaco,
|
||||||
|
justify="right",
|
||||||
|
anchor="w",
|
||||||
|
padx=10,
|
||||||
|
pady=0
|
||||||
|
)
|
||||||
|
|
||||||
|
label_todo_close = tk.Button( todo,
|
||||||
|
text="Schließen",
|
||||||
|
command=close_todo,
|
||||||
|
font=segoeui,
|
||||||
|
padx=10,
|
||||||
|
pady=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
# setup grid
|
||||||
|
label_todo_tldr.grid(
|
||||||
|
row=1,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
padx=0,
|
||||||
|
pady=5,
|
||||||
|
sticky="nsew"
|
||||||
|
)
|
||||||
|
|
||||||
|
seperator.grid(
|
||||||
|
row=2,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
pady=5,
|
||||||
|
sticky="ew"
|
||||||
|
)
|
||||||
|
|
||||||
|
label_todo.grid(
|
||||||
|
row=3,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
pady=5,
|
||||||
|
sticky="nsew"
|
||||||
|
)
|
||||||
|
|
||||||
|
seperator2.grid(
|
||||||
|
row=4,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
pady=5,
|
||||||
|
sticky="ew"
|
||||||
|
)
|
||||||
|
|
||||||
|
label_todo_changelog.grid(
|
||||||
|
row=5,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
pady=5,
|
||||||
|
sticky="nsew"
|
||||||
|
)
|
||||||
|
|
||||||
|
label_todo_percentage_bar_text.grid(
|
||||||
|
row=6,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
sticky="ew"
|
||||||
|
)
|
||||||
|
|
||||||
|
progress_bar.grid(
|
||||||
|
row=7,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
pady=5,
|
||||||
|
sticky="ew"
|
||||||
|
)
|
||||||
|
|
||||||
|
label_todo_close.grid(
|
||||||
|
row=8,
|
||||||
|
column=0,
|
||||||
|
columnspan=2,
|
||||||
|
pady=5,
|
||||||
|
sticky="nsew"
|
||||||
|
)
|
||||||
|
|
||||||
# TGTG Credits
|
# TGTG Credits
|
||||||
def TGTG_credits(self):
|
def TGTG_credits(self):
|
||||||
@@ -714,7 +850,7 @@ class PfandCalculator:
|
|||||||
about_uscan,
|
about_uscan,
|
||||||
text=(
|
text=(
|
||||||
"µScan - Der bessere Barcode Scanner\n"
|
"µScan - Der bessere Barcode Scanner\n"
|
||||||
"Version 2.2.2\n"
|
"Version 2.3.5\n"
|
||||||
"µScan erfordert einen UI Reload (Strg+R) in der Root Anwendung\n"
|
"µScan erfordert einen UI Reload (Strg+R) in der Root Anwendung\n"
|
||||||
"µScan ist für mehrere Barcodes gemacht, die in einer kurzen Zeit gescannt werden sollten\n"
|
"µScan ist für mehrere Barcodes gemacht, die in einer kurzen Zeit gescannt werden sollten\n"
|
||||||
"Beachte das µScan eine Kamera benötigt die mindestens 30FPS aufnehmen kann (Process-FPS können eingestellt werden!)"
|
"Beachte das µScan eine Kamera benötigt die mindestens 30FPS aufnehmen kann (Process-FPS können eingestellt werden!)"
|
||||||
@@ -1736,7 +1872,6 @@ class PfandCalculator:
|
|||||||
"Kasten",
|
"Kasten",
|
||||||
"Dose",
|
"Dose",
|
||||||
"Plastikflasche",
|
"Plastikflasche",
|
||||||
"Monster",
|
|
||||||
"Joghurt Glas",
|
"Joghurt Glas",
|
||||||
]
|
]
|
||||||
self.PRICES = {
|
self.PRICES = {
|
||||||
@@ -1745,7 +1880,6 @@ class PfandCalculator:
|
|||||||
"Kasten": 3.00,
|
"Kasten": 3.00,
|
||||||
"Dose": 0.25,
|
"Dose": 0.25,
|
||||||
"Plastikflasche": 0.25,
|
"Plastikflasche": 0.25,
|
||||||
"Monster": 0.25,
|
|
||||||
"Joghurt Glas": 0.17,
|
"Joghurt Glas": 0.17,
|
||||||
}
|
}
|
||||||
self.save_products()
|
self.save_products()
|
||||||
@@ -2093,4 +2227,4 @@ class PfandCalculator:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
PfandCalculator.launch(True)
|
PfandCalculator.launch(False)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# µScan V2.2.2
|
# µScan V2.3.0 - Verbesserte Deutsche Version
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, simpledialog, messagebox
|
from tkinter import ttk, simpledialog, messagebox
|
||||||
import cv2
|
import cv2
|
||||||
@@ -10,81 +10,328 @@ import queue
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
class PfandScanner:
|
class PfandScanner:
|
||||||
def __init__(self, window, window_title):
|
def __init__(self, window, window_title):
|
||||||
self.window = window
|
self.window = window
|
||||||
self.window.title(window_title)
|
self.window.title(window_title)
|
||||||
self.window.geometry("1920x1080")
|
self.window.geometry("1600x900")
|
||||||
self.window.minsize(960, 540)
|
self.window.minsize(1200, 700)
|
||||||
|
self.window.configure(bg='#f0f0f0')
|
||||||
|
|
||||||
|
# Configure main window grid
|
||||||
self.window.columnconfigure(0, weight=1)
|
self.window.columnconfigure(0, weight=1)
|
||||||
self.window.rowconfigure(0, weight=1)
|
self.window.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Style configuration
|
||||||
|
self.setup_styles()
|
||||||
|
|
||||||
self.data_file = "quantities.json"
|
self.data_file = "quantities.json"
|
||||||
self.load_json()
|
self.products_file = "products.json"
|
||||||
|
self.load_data()
|
||||||
|
|
||||||
self.barcode_times = {}
|
self.barcode_times = {}
|
||||||
self.prompted_barcodes = set()
|
self.prompted_barcodes = set()
|
||||||
|
self.current_barcodes = [] # Store current frame's barcodes for outlining
|
||||||
|
|
||||||
self.selected_device_index = tk.IntVar(value=0)
|
self.selected_device_index = tk.IntVar(value=0)
|
||||||
self.last_process_time = time.time()
|
self.last_process_time = time.time()
|
||||||
|
|
||||||
# FPS Einstellung ist hier!
|
# FPS Setting
|
||||||
# FPS Setting is here!
|
self.process_interval = 0.15 # Improved processing speed
|
||||||
self.process_interval = 0.30 # 30 FPS
|
|
||||||
|
# Collapsible scan list state
|
||||||
|
self.scan_list_collapsed = tk.BooleanVar(value=False)
|
||||||
|
|
||||||
|
# Threading control
|
||||||
|
self.running = True
|
||||||
|
self.camera_thread = None
|
||||||
|
self.process_thread = None
|
||||||
|
|
||||||
self.init_gui()
|
self.init_gui()
|
||||||
self.init_camera()
|
self.init_camera()
|
||||||
|
|
||||||
self.queue = queue.Queue()
|
self.queue = queue.Queue()
|
||||||
|
self.frame_queue = queue.Queue(maxsize=2) # Limit queue size to prevent memory issues
|
||||||
self.pfand_values = {
|
self.pfand_values = {
|
||||||
"EINWEG": 0.25,
|
"EINWEG": 0.25,
|
||||||
"MEHRWEG": 0.15,
|
"MEHRWEG": 0.15,
|
||||||
"DOSE": 0.25,
|
"DOSE": 0.25,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_preview()
|
# Start threads
|
||||||
|
self.start_threads()
|
||||||
|
|
||||||
self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
|
self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||||
self.process_queue()
|
|
||||||
|
def setup_styles(self):
|
||||||
|
self.style = ttk.Style()
|
||||||
|
self.style.theme_use('clam')
|
||||||
|
|
||||||
|
# Configure colors
|
||||||
|
self.colors = {
|
||||||
|
'primary': '#2c3e50',
|
||||||
|
'secondary': '#3498db',
|
||||||
|
'success': '#27ae60',
|
||||||
|
'warning': '#f39c12',
|
||||||
|
'danger': '#e74c3c',
|
||||||
|
'light': '#ecf0f1',
|
||||||
|
'dark': '#34495e'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure custom styles
|
||||||
|
self.style.configure('Title.TLabel', font=('Segoe UI', 16, 'bold'), foreground=self.colors['primary'])
|
||||||
|
self.style.configure('Heading.TLabel', font=('Segoe UI', 12, 'bold'), foreground=self.colors['dark'])
|
||||||
|
self.style.configure('Info.TLabel', font=('Segoe UI', 10), foreground=self.colors['dark'])
|
||||||
|
self.style.configure('Success.TLabel', font=('Segoe UI', 10, 'bold'), foreground=self.colors['success'])
|
||||||
|
|
||||||
|
self.style.configure('Custom.TLabelFrame', relief='solid', borderwidth=1)
|
||||||
|
self.style.configure('Camera.TFrame', relief='solid', borderwidth=2)
|
||||||
|
|
||||||
|
def load_data(self):
|
||||||
|
# Load products
|
||||||
|
if os.path.exists(self.products_file):
|
||||||
|
try:
|
||||||
|
with open(self.products_file, 'r', encoding='utf-8') as f:
|
||||||
|
products_data = json.load(f)
|
||||||
|
self.products = products_data.get("products", [])
|
||||||
|
self.prices = products_data.get("prices", {})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Laden der Produkte: {e}")
|
||||||
|
self.products = []
|
||||||
|
self.prices = {}
|
||||||
|
else:
|
||||||
|
self.products = []
|
||||||
|
self.prices = {}
|
||||||
|
|
||||||
|
# Load quantities
|
||||||
|
if os.path.exists(self.data_file):
|
||||||
|
try:
|
||||||
|
with open(self.data_file, 'r', encoding='utf-8') as f:
|
||||||
|
self.quantities = json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Laden der Mengen: {e}")
|
||||||
|
self.quantities = {}
|
||||||
|
else:
|
||||||
|
self.quantities = {}
|
||||||
|
|
||||||
|
def save_json(self):
|
||||||
|
"""Save quantities to quantities.json"""
|
||||||
|
try:
|
||||||
|
with open(self.data_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.quantities, f, indent=4, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Speichern der Mengen: {e}")
|
||||||
|
|
||||||
def init_gui(self):
|
def init_gui(self):
|
||||||
self.main_frame = ttk.Frame(self.window)
|
# Main container
|
||||||
|
self.main_frame = ttk.Frame(self.window, padding="10")
|
||||||
self.main_frame.grid(sticky="nsew")
|
self.main_frame.grid(sticky="nsew")
|
||||||
self.main_frame.columnconfigure(0, weight=3)
|
self.main_frame.columnconfigure(1, weight=2) # Camera gets more space
|
||||||
self.main_frame.columnconfigure(1, weight=1)
|
self.main_frame.columnconfigure(0, weight=1) # Controls
|
||||||
self.main_frame.columnconfigure(2, weight=3)
|
self.main_frame.columnconfigure(2, weight=1) # Info
|
||||||
self.main_frame.rowconfigure(0, weight=1)
|
self.main_frame.rowconfigure(1, weight=1)
|
||||||
|
|
||||||
self.camera_frame = ttk.Frame(self.main_frame)
|
# Header
|
||||||
self.camera_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
|
header_frame = ttk.Frame(self.main_frame)
|
||||||
|
header_frame.grid(row=0, column=0, columnspan=3, sticky="ew", pady=(0, 10))
|
||||||
|
|
||||||
self.control_frame = ttk.Frame(self.main_frame)
|
title_label = ttk.Label(header_frame, text="µScan", style='Title.TLabel')
|
||||||
self.control_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
|
title_label.pack(side="left")
|
||||||
|
|
||||||
self.info_frame = ttk.Frame(self.main_frame)
|
status_label = ttk.Label(header_frame, text="v2.3.5", style='Info.TLabel')
|
||||||
self.info_frame.grid(row=0, column=2, padx=5, pady=5, sticky="nsew")
|
status_label.pack(side="right")
|
||||||
|
|
||||||
self.camera_label = ttk.Label(self.camera_frame)
|
# Control Panel (Left)
|
||||||
self.camera_label.pack(expand=True, fill="both")
|
self.control_frame = ttk.LabelFrame(self.main_frame, text="Steuerung", padding="10")
|
||||||
|
self.control_frame.grid(row=1, column=0, padx=(0, 10), sticky="nsew")
|
||||||
|
|
||||||
|
# Camera View (Center)
|
||||||
|
self.camera_frame = ttk.LabelFrame(self.main_frame, text="Kameraansicht", padding="5")
|
||||||
|
self.camera_frame.grid(row=1, column=1, padx=5, sticky="nsew")
|
||||||
|
self.camera_frame.columnconfigure(0, weight=1)
|
||||||
|
self.camera_frame.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Info Panel (Right) - now collapsible
|
||||||
|
self.info_frame = ttk.LabelFrame(self.main_frame, text="Scan-Ergebnisse", padding="10")
|
||||||
|
self.info_frame.grid(row=1, column=2, padx=(10, 0), sticky="nsew")
|
||||||
|
|
||||||
|
# Camera display
|
||||||
|
camera_container = ttk.Frame(self.camera_frame, relief='solid', borderwidth=2)
|
||||||
|
camera_container.grid(sticky="nsew", padx=5, pady=5)
|
||||||
|
camera_container.columnconfigure(0, weight=1)
|
||||||
|
camera_container.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.camera_label = ttk.Label(camera_container, text="Kamera wird geladen...", anchor="center")
|
||||||
|
self.camera_label.grid(sticky="nsew")
|
||||||
|
|
||||||
self.init_device_selector()
|
self.init_device_selector()
|
||||||
self.init_controls()
|
self.init_controls()
|
||||||
self.init_treeview()
|
self.init_treeview()
|
||||||
|
self.init_statistics()
|
||||||
|
|
||||||
def init_device_selector(self):
|
def init_device_selector(self):
|
||||||
device_frame = ttk.LabelFrame(self.control_frame, text="Video Device")
|
device_frame = ttk.LabelFrame(self.control_frame, text="Kamera-Einstellungen", padding="10")
|
||||||
device_frame.pack(pady=5, padx=5, fill="x")
|
device_frame.pack(fill="x", pady=(0, 10))
|
||||||
|
|
||||||
ttk.Label(device_frame, text="Choose Camera:").pack(anchor="w", padx=5)
|
ttk.Label(device_frame, text="Kamera auswählen:", style='Heading.TLabel').pack(anchor="w")
|
||||||
|
|
||||||
self.device_combo = ttk.Combobox(device_frame, state="readonly")
|
self.device_combo = ttk.Combobox(device_frame, state="readonly")
|
||||||
self.device_combo.pack(fill="x", padx=5)
|
self.device_combo.pack(fill="x", pady=(5, 10))
|
||||||
|
|
||||||
available_devices = self.list_video_devices()
|
available_devices = self.list_video_devices()
|
||||||
self.device_combo['values'] = [f"Camera {i}" for i in available_devices]
|
self.device_combo['values'] = [f"Kamera {i}" for i in available_devices]
|
||||||
|
if available_devices:
|
||||||
self.device_combo.current(0)
|
self.device_combo.current(0)
|
||||||
self.device_combo.bind("<<ComboboxSelected>>", self.change_camera)
|
self.device_combo.bind("<<ComboboxSelected>>", self.change_camera)
|
||||||
|
|
||||||
|
# Camera status
|
||||||
|
self.camera_status = ttk.Label(device_frame, text="Status: Initialisierung...", style='Info.TLabel')
|
||||||
|
self.camera_status.pack(anchor="w")
|
||||||
|
|
||||||
|
def init_controls(self):
|
||||||
|
# Focus controls
|
||||||
|
focus_frame = ttk.LabelFrame(self.control_frame, text="Fokus-Steuerung", padding="10")
|
||||||
|
focus_frame.pack(fill="x", pady=(0, 10))
|
||||||
|
|
||||||
|
self.autofocus_var = tk.BooleanVar(value=True)
|
||||||
|
self.autofocus_check = ttk.Checkbutton(
|
||||||
|
focus_frame, text="Auto-Fokus", variable=self.autofocus_var, command=self.toggle_autofocus)
|
||||||
|
self.autofocus_check.pack(anchor="w", pady=(0, 5))
|
||||||
|
|
||||||
|
ttk.Label(focus_frame, text="Manueller Fokus:").pack(anchor="w")
|
||||||
|
self.focus_slider = ttk.Scale(focus_frame, from_=0, to=255, orient="horizontal")
|
||||||
|
self.focus_slider.set(0)
|
||||||
|
self.focus_slider.pack(fill="x", pady=(0, 5))
|
||||||
|
|
||||||
|
# Image processing controls
|
||||||
|
process_frame = ttk.LabelFrame(self.control_frame, text="Bildverbesserung", padding="10")
|
||||||
|
process_frame.pack(fill="x", pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Label(process_frame, text="Helligkeit:").pack(anchor="w")
|
||||||
|
self.brightness_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal")
|
||||||
|
self.brightness_slider.set(50)
|
||||||
|
self.brightness_slider.pack(fill="x", pady=(0, 5))
|
||||||
|
|
||||||
|
ttk.Label(process_frame, text="Kontrast:").pack(anchor="w")
|
||||||
|
self.contrast_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal")
|
||||||
|
self.contrast_slider.set(50)
|
||||||
|
self.contrast_slider.pack(fill="x", pady=(0, 10))
|
||||||
|
|
||||||
|
# Scan options
|
||||||
|
scan_frame = ttk.LabelFrame(self.control_frame, text="Scan-Optionen", padding="10")
|
||||||
|
scan_frame.pack(fill="x")
|
||||||
|
|
||||||
|
self.outline_var = tk.BooleanVar(value=True)
|
||||||
|
ttk.Checkbutton(scan_frame, text="Barcodes umranden", variable=self.outline_var).pack(anchor="w")
|
||||||
|
|
||||||
|
self.beep_var = tk.BooleanVar(value=True)
|
||||||
|
ttk.Checkbutton(scan_frame, text="Ton bei Scan", variable=self.beep_var).pack(anchor="w")
|
||||||
|
|
||||||
|
def init_statistics(self):
|
||||||
|
"""Initialize statistics panel"""
|
||||||
|
stats_frame = ttk.LabelFrame(self.info_frame, text="Statistiken", padding="10")
|
||||||
|
stats_frame.pack(fill="x", pady=(0, 10))
|
||||||
|
|
||||||
|
self.total_scans_label = ttk.Label(stats_frame, text="Gesamte Scans: 0", style='Info.TLabel')
|
||||||
|
self.total_scans_label.pack(anchor="w")
|
||||||
|
|
||||||
|
self.total_value_label = ttk.Label(stats_frame, text="Gesamtwert: €0,00", style='Success.TLabel')
|
||||||
|
self.total_value_label.pack(anchor="w")
|
||||||
|
|
||||||
|
self.session_time_label = ttk.Label(stats_frame, text="Sitzungsdauer: 00:00", style='Info.TLabel')
|
||||||
|
self.session_time_label.pack(anchor="w")
|
||||||
|
|
||||||
|
self.session_start = datetime.now()
|
||||||
|
self.total_scans = 0
|
||||||
|
self.total_value = 0.0
|
||||||
|
|
||||||
|
def init_treeview(self):
|
||||||
|
# Header frame with collapse button
|
||||||
|
tree_header_frame = ttk.Frame(self.info_frame)
|
||||||
|
tree_header_frame.pack(fill="x", pady=(0, 5))
|
||||||
|
|
||||||
|
# Collapse button
|
||||||
|
self.collapse_button = ttk.Button(
|
||||||
|
tree_header_frame,
|
||||||
|
text="▼ Scan-Liste",
|
||||||
|
command=self.toggle_scan_list,
|
||||||
|
width=15
|
||||||
|
)
|
||||||
|
self.collapse_button.pack(side="left")
|
||||||
|
|
||||||
|
# Clear button
|
||||||
|
clear_button = ttk.Button(
|
||||||
|
tree_header_frame,
|
||||||
|
text="Liste löschen",
|
||||||
|
command=self.clear_scan_list
|
||||||
|
)
|
||||||
|
clear_button.pack(side="right")
|
||||||
|
|
||||||
|
# Treeview frame (collapsible)
|
||||||
|
self.tree_container = ttk.Frame(self.info_frame)
|
||||||
|
self.tree_container.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
self.tree = ttk.Treeview(
|
||||||
|
self.tree_container,
|
||||||
|
columns=("Zeit", "Barcode", "Typ", "Pfand"),
|
||||||
|
show="headings",
|
||||||
|
height=15
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure columns
|
||||||
|
self.tree.heading("Zeit", text="Zeit")
|
||||||
|
self.tree.heading("Barcode", text="Barcode")
|
||||||
|
self.tree.heading("Typ", text="Typ")
|
||||||
|
self.tree.heading("Pfand", text="Pfand")
|
||||||
|
|
||||||
|
self.tree.column("Zeit", width=100, minwidth=80)
|
||||||
|
self.tree.column("Barcode", width=120, minwidth=100)
|
||||||
|
self.tree.column("Typ", width=80, minwidth=60)
|
||||||
|
self.tree.column("Pfand", width=70, minwidth=50)
|
||||||
|
|
||||||
|
# Scrollbars
|
||||||
|
v_scrollbar = ttk.Scrollbar(self.tree_container, orient="vertical", command=self.tree.yview)
|
||||||
|
h_scrollbar = ttk.Scrollbar(self.tree_container, orient="horizontal", command=self.tree.xview)
|
||||||
|
|
||||||
|
self.tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
|
||||||
|
|
||||||
|
# Grid layout
|
||||||
|
self.tree.grid(row=0, column=0, sticky="nsew")
|
||||||
|
v_scrollbar.grid(row=0, column=1, sticky="ns")
|
||||||
|
h_scrollbar.grid(row=1, column=0, sticky="ew")
|
||||||
|
|
||||||
|
self.tree_container.columnconfigure(0, weight=1)
|
||||||
|
self.tree_container.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Alternate row colors
|
||||||
|
self.tree.tag_configure('oddrow', background='#f9f9f9')
|
||||||
|
self.tree.tag_configure('evenrow', background='white')
|
||||||
|
|
||||||
|
def toggle_scan_list(self):
|
||||||
|
"""Toggle the visibility of the scan list"""
|
||||||
|
if self.scan_list_collapsed.get():
|
||||||
|
# Expand
|
||||||
|
self.tree_container.pack(fill="both", expand=True)
|
||||||
|
self.collapse_button.configure(text="▼ Scan-Liste")
|
||||||
|
self.scan_list_collapsed.set(False)
|
||||||
|
# Resize window columns
|
||||||
|
self.main_frame.columnconfigure(2, weight=1)
|
||||||
|
else:
|
||||||
|
# Collapse
|
||||||
|
self.tree_container.pack_forget()
|
||||||
|
self.collapse_button.configure(text="▶ Scan-Liste")
|
||||||
|
self.scan_list_collapsed.set(True)
|
||||||
|
# Make info panel smaller
|
||||||
|
self.main_frame.columnconfigure(2, weight=0, minsize=200)
|
||||||
|
|
||||||
|
def clear_scan_list(self):
|
||||||
|
"""Clear all items from the scan list"""
|
||||||
|
if messagebox.askyesno("Liste löschen", "Möchten Sie wirklich alle Einträge aus der Scan-Liste löschen?"):
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
self.tree.delete(item)
|
||||||
|
|
||||||
def list_video_devices(self, max_devices=10):
|
def list_video_devices(self, max_devices=10):
|
||||||
available = []
|
available = []
|
||||||
for i in range(max_devices):
|
for i in range(max_devices):
|
||||||
@@ -99,65 +346,21 @@ class PfandScanner:
|
|||||||
self.selected_device_index.set(index)
|
self.selected_device_index.set(index)
|
||||||
self.init_camera()
|
self.init_camera()
|
||||||
|
|
||||||
def init_controls(self):
|
|
||||||
focus_frame = ttk.LabelFrame(self.control_frame, text="Camera Controls")
|
|
||||||
focus_frame.pack(pady=5, padx=5, fill="x")
|
|
||||||
|
|
||||||
ttk.Label(focus_frame, text="Focus:").pack(pady=2)
|
|
||||||
self.focus_slider = ttk.Scale(focus_frame, from_=0, to=255, orient="horizontal")
|
|
||||||
self.focus_slider.set(0)
|
|
||||||
self.focus_slider.pack(pady=2, padx=5, fill="x")
|
|
||||||
|
|
||||||
self.autofocus_var = tk.BooleanVar(value=True)
|
|
||||||
self.autofocus_check = ttk.Checkbutton(
|
|
||||||
focus_frame, text="Autofocus", variable=self.autofocus_var, command=self.toggle_autofocus)
|
|
||||||
self.autofocus_check.pack(pady=2)
|
|
||||||
|
|
||||||
process_frame = ttk.LabelFrame(self.control_frame, text="Image Processing")
|
|
||||||
process_frame.pack(pady=5, padx=5, fill="x")
|
|
||||||
|
|
||||||
ttk.Label(process_frame, text="Brightness:").pack(pady=2)
|
|
||||||
self.brightness_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal")
|
|
||||||
self.brightness_slider.set(50)
|
|
||||||
self.brightness_slider.pack(pady=2, padx=5, fill="x")
|
|
||||||
|
|
||||||
ttk.Label(process_frame, text="Contrast:").pack(pady=2)
|
|
||||||
self.contrast_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal")
|
|
||||||
self.contrast_slider.set(50)
|
|
||||||
self.contrast_slider.pack(pady=2, padx=5, fill="x")
|
|
||||||
|
|
||||||
def init_treeview(self):
|
|
||||||
self.tree = ttk.Treeview(self.info_frame, columns=("Time", "Barcode", "Type", "Deposit"), show="headings")
|
|
||||||
for col in ["Time", "Barcode", "Type", "Deposit"]:
|
|
||||||
self.tree.heading(col, text=col)
|
|
||||||
self.tree.column("Time", width=150)
|
|
||||||
self.tree.column("Barcode", width=200)
|
|
||||||
self.tree.column("Type", width=100)
|
|
||||||
self.tree.column("Deposit", width=100)
|
|
||||||
self.tree.pack(fill="both", expand=True)
|
|
||||||
|
|
||||||
scrollbar = ttk.Scrollbar(self.info_frame, orient="vertical", command=self.tree.yview)
|
|
||||||
scrollbar.pack(side="right", fill="y")
|
|
||||||
self.tree.configure(yscrollcommand=scrollbar.set)
|
|
||||||
def init_camera(self):
|
def init_camera(self):
|
||||||
if hasattr(self, 'cap') and self.cap and self.cap.isOpened():
|
if hasattr(self, 'cap') and self.cap and self.cap.isOpened():
|
||||||
self.cap.release()
|
self.cap.release()
|
||||||
|
|
||||||
device_index = self.selected_device_index.get()
|
device_index = self.selected_device_index.get()
|
||||||
self.cap = cv2.VideoCapture(device_index, cv2.CAP_DSHOW if os.name == 'nt' else 0)
|
self.cap = cv2.VideoCapture(device_index, cv2.CAP_DSHOW if os.name == 'nt' else 0)
|
||||||
|
|
||||||
|
if self.cap.isOpened():
|
||||||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
|
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
|
||||||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
|
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
|
||||||
|
self.cap.set(cv2.CAP_PROP_FPS, 30)
|
||||||
self.toggle_autofocus()
|
self.toggle_autofocus()
|
||||||
|
self.camera_status.configure(text="Status: Verbunden ✓", foreground=self.colors['success'])
|
||||||
def load_json(self):
|
|
||||||
if os.path.exists(self.data_file):
|
|
||||||
with open(self.data_file, 'r') as f:
|
|
||||||
self.quantities = json.load(f)
|
|
||||||
else:
|
else:
|
||||||
self.quantities = {}
|
self.camera_status.configure(text="Status: Fehler ✗", foreground=self.colors['danger'])
|
||||||
|
|
||||||
def save_json(self):
|
|
||||||
with open(self.data_file, 'w') as f:
|
|
||||||
json.dump(self.quantities, f, indent=4)
|
|
||||||
|
|
||||||
def toggle_autofocus(self):
|
def toggle_autofocus(self):
|
||||||
if self.cap:
|
if self.cap:
|
||||||
@@ -178,8 +381,67 @@ class PfandScanner:
|
|||||||
return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||||
cv2.THRESH_BINARY, 11, 2)
|
cv2.THRESH_BINARY, 11, 2)
|
||||||
|
|
||||||
def update_preview(self):
|
def draw_barcode_outline(self, frame, barcodes):
|
||||||
|
if not self.outline_var.get():
|
||||||
|
return frame
|
||||||
|
|
||||||
|
for barcode in barcodes:
|
||||||
|
# Get barcode polygon points
|
||||||
|
points = barcode.polygon
|
||||||
|
if len(points) == 4:
|
||||||
|
# Convert to numpy array
|
||||||
|
pts = np.array([[point.x, point.y] for point in points], np.int32)
|
||||||
|
pts = pts.reshape((-1, 1, 2))
|
||||||
|
|
||||||
|
# Draw colored outline based on barcode type
|
||||||
|
barcode_data = barcode.data.decode('utf-8')
|
||||||
|
if len(barcode_data) == 13:
|
||||||
|
color = (0, 255, 0) # Green for EINWEG
|
||||||
|
elif len(barcode_data) == 8:
|
||||||
|
color = (255, 0, 0) # Blue for MEHRWEG
|
||||||
|
else:
|
||||||
|
color = (0, 165, 255) # Orange for DOSE
|
||||||
|
|
||||||
|
# Draw outline
|
||||||
|
cv2.polylines(frame, [pts], True, color, 3)
|
||||||
|
|
||||||
|
# Add label
|
||||||
|
rect = cv2.boundingRect(pts)
|
||||||
|
label_pos = (rect[0], rect[1] - 10)
|
||||||
|
pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE"
|
||||||
|
deposit = self.pfand_values.get(pfand_type, 0.00)
|
||||||
|
label = f"{pfand_type}: €{deposit:.2f}"
|
||||||
|
|
||||||
|
# Draw label background
|
||||||
|
(text_width, text_height), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
|
||||||
|
cv2.rectangle(frame, (label_pos[0], label_pos[1] - text_height - 5),
|
||||||
|
(label_pos[0] + text_width, label_pos[1] + 5), color, -1)
|
||||||
|
|
||||||
|
# Draw label text
|
||||||
|
cv2.putText(frame, label, label_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def start_threads(self):
|
||||||
|
"""Start camera and processing threads"""
|
||||||
|
self.camera_thread = threading.Thread(target=self.camera_worker, daemon=True)
|
||||||
|
self.camera_thread.start()
|
||||||
|
|
||||||
|
self.process_thread = threading.Thread(target=self.process_worker, daemon=True)
|
||||||
|
self.process_thread.start()
|
||||||
|
|
||||||
|
# Start UI update loop
|
||||||
|
self.update_preview()
|
||||||
|
self.process_queue()
|
||||||
|
|
||||||
|
def camera_worker(self):
|
||||||
|
"""Worker thread for camera capture and processing"""
|
||||||
|
while self.running:
|
||||||
try:
|
try:
|
||||||
|
if not hasattr(self, 'cap') or not self.cap.isOpened():
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
ret, frame = self.cap.read()
|
ret, frame = self.cap.read()
|
||||||
if ret:
|
if ret:
|
||||||
if not self.autofocus_var.get():
|
if not self.autofocus_var.get():
|
||||||
@@ -189,63 +451,51 @@ class PfandScanner:
|
|||||||
if current_time - self.last_process_time >= self.process_interval:
|
if current_time - self.last_process_time >= self.process_interval:
|
||||||
processed_frame = self.adjust_image(frame)
|
processed_frame = self.adjust_image(frame)
|
||||||
barcodes = decode(processed_frame) or decode(frame)
|
barcodes = decode(processed_frame) or decode(frame)
|
||||||
|
self.current_barcodes = barcodes
|
||||||
|
|
||||||
for barcode in barcodes:
|
for barcode in barcodes:
|
||||||
barcode_data = barcode.data.decode('utf-8')
|
barcode_data = barcode.data.decode('utf-8')
|
||||||
self.queue.put(barcode_data)
|
self.queue.put(barcode_data)
|
||||||
self.last_process_time = current_time
|
self.last_process_time = current_time
|
||||||
|
|
||||||
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
|
# Draw barcode outlines
|
||||||
img = Image.fromarray(cv2image)
|
frame_with_outlines = self.draw_barcode_outline(frame.copy(), self.current_barcodes)
|
||||||
imgtk = ImageTk.PhotoImage(image=img)
|
|
||||||
self.camera_label.imgtk = imgtk
|
# Put frame in queue for UI thread
|
||||||
self.camera_label.configure(image=imgtk)
|
try:
|
||||||
|
if self.frame_queue.full():
|
||||||
|
self.frame_queue.get_nowait() # Discard old frame
|
||||||
|
self.frame_queue.put(frame_with_outlines, timeout=0.1)
|
||||||
|
except queue.Full:
|
||||||
|
pass # Skip this frame if queue is full
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in video preview: {e}")
|
print(f"Fehler in der Kamera-Verarbeitung: {e}")
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
self.window.after(10, self.update_preview) # ~100 FPS preview
|
def process_worker(self):
|
||||||
|
"""Worker thread for barcode processing"""
|
||||||
def show_product_selection(self, barcode_data):
|
while self.running:
|
||||||
if hasattr(self, 'product_win') and self.product_win.winfo_exists():
|
|
||||||
return
|
|
||||||
|
|
||||||
self.product_win = tk.Toplevel(self.window)
|
|
||||||
self.product_win.title("Produktwahl")
|
|
||||||
|
|
||||||
ttk.Label(self.product_win, text=f"Welches Produkt soll dem Barcode '{barcode_data}' zugeordnet werden?").pack(pady=5)
|
|
||||||
|
|
||||||
selected_product = tk.StringVar()
|
|
||||||
for prod in self.quantities:
|
|
||||||
ttk.Radiobutton(self.product_win, text=prod, variable=selected_product, value=prod).pack(anchor='w')
|
|
||||||
|
|
||||||
def confirm():
|
|
||||||
prod = selected_product.get()
|
|
||||||
if prod:
|
|
||||||
self.quantities[prod] = self.quantities.get(prod, 0) + 1
|
|
||||||
self.save_json()
|
|
||||||
self.product_win.destroy()
|
|
||||||
else:
|
|
||||||
messagebox.showwarning("Keine Auswahl", "Bitte ein Produkt auswählen.")
|
|
||||||
|
|
||||||
ttk.Button(self.product_win, text="Bestätigen", command=confirm).pack(pady=5)
|
|
||||||
|
|
||||||
def process_queue(self):
|
|
||||||
try:
|
try:
|
||||||
barcode_data = self.queue.get(timeout=0.1)
|
barcode_data = self.queue.get(timeout=0.1)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
timestamps = self.barcode_times.get(barcode_data, [])
|
timestamps = self.barcode_times.get(barcode_data, [])
|
||||||
timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)]
|
timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)]
|
||||||
if len(timestamps) >= 3:
|
if len(timestamps) >= 3:
|
||||||
return
|
continue
|
||||||
timestamps.append(now)
|
timestamps.append(now)
|
||||||
self.barcode_times[barcode_data] = timestamps
|
self.barcode_times[barcode_data] = timestamps
|
||||||
|
|
||||||
current_time = now.strftime("%Y-%m-%d %H:%M:%S")
|
current_time = now.strftime("%H:%M:%S")
|
||||||
pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE"
|
pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE"
|
||||||
deposit = self.pfand_values.get(pfand_type, 0.00)
|
deposit = self.pfand_values.get(pfand_type, 0.00)
|
||||||
|
|
||||||
self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"{deposit:.2f}"))
|
# Update UI in main thread
|
||||||
|
self.window.after(0, self.add_to_treeview, current_time, barcode_data, pfand_type, deposit)
|
||||||
|
|
||||||
|
# Show product selection dialog
|
||||||
if barcode_data not in self.prompted_barcodes:
|
if barcode_data not in self.prompted_barcodes:
|
||||||
self.prompted_barcodes.add(barcode_data)
|
self.prompted_barcodes.add(barcode_data)
|
||||||
self.window.after(0, self.show_product_selection, barcode_data)
|
self.window.after(0, self.show_product_selection, barcode_data)
|
||||||
@@ -253,16 +503,258 @@ class PfandScanner:
|
|||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in queue processing: {e}")
|
print(f"Fehler in der Warteschlangenverarbeitung: {e}")
|
||||||
finally:
|
|
||||||
|
def add_to_treeview(self, current_time, barcode_data, pfand_type, deposit):
|
||||||
|
"""Add item to treeview (called from main thread)"""
|
||||||
|
# Determine row color
|
||||||
|
row_count = len(self.tree.get_children())
|
||||||
|
tag = 'evenrow' if row_count % 2 == 0 else 'oddrow'
|
||||||
|
|
||||||
|
self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"€{deposit:.2f}"), tags=(tag,))
|
||||||
|
|
||||||
|
# Update statistics
|
||||||
|
self.total_scans += 1
|
||||||
|
self.update_statistics()
|
||||||
|
|
||||||
|
# Sound notification
|
||||||
|
if self.beep_var.get():
|
||||||
|
self.window.bell()
|
||||||
|
|
||||||
|
def update_preview(self):
|
||||||
|
"""Update camera preview (called from main thread)"""
|
||||||
|
try:
|
||||||
|
# Get frame from queue
|
||||||
|
frame = self.frame_queue.get_nowait()
|
||||||
|
|
||||||
|
# Convert and display
|
||||||
|
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
|
||||||
|
img = Image.fromarray(cv2image)
|
||||||
|
|
||||||
|
# Resize to fit label while maintaining aspect ratio
|
||||||
|
label_width = self.camera_label.winfo_width()
|
||||||
|
label_height = self.camera_label.winfo_height()
|
||||||
|
if label_width > 1 and label_height > 1:
|
||||||
|
img.thumbnail((label_width, label_height), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
imgtk = ImageTk.PhotoImage(image=img)
|
||||||
|
self.camera_label.imgtk = imgtk
|
||||||
|
self.camera_label.configure(image=imgtk)
|
||||||
|
|
||||||
|
except queue.Empty:
|
||||||
|
pass # No new frame available
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler in der Video-Vorschau: {e}")
|
||||||
|
|
||||||
|
# Schedule next update
|
||||||
|
if self.running:
|
||||||
|
self.window.after(30, self.update_preview)
|
||||||
|
|
||||||
|
def update_statistics(self):
|
||||||
|
session_time = datetime.now() - self.session_start
|
||||||
|
hours, remainder = divmod(int(session_time.total_seconds()), 3600)
|
||||||
|
minutes, _ = divmod(remainder, 60)
|
||||||
|
time_str = f"{hours:02d}:{minutes:02d}"
|
||||||
|
|
||||||
|
self.session_time_label.configure(text=f"Sitzungsdauer: {time_str}")
|
||||||
|
self.total_scans_label.configure(text=f"Gesamte Scans: {self.total_scans}")
|
||||||
|
self.total_value_label.configure(text=f"Gesamtwert: €{self.total_value:.2f}")
|
||||||
|
|
||||||
|
def show_product_selection(self, barcode_data):
|
||||||
|
if hasattr(self, 'product_win') and self.product_win.winfo_exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.product_win = tk.Toplevel(self.window)
|
||||||
|
self.product_win.title("Produkt auswählen")
|
||||||
|
self.product_win.geometry("500x400")
|
||||||
|
self.product_win.minsize(450, 350)
|
||||||
|
self.product_win.resizable(True, True)
|
||||||
|
|
||||||
|
# Center the window
|
||||||
|
self.product_win.transient(self.window)
|
||||||
|
self.product_win.grab_set()
|
||||||
|
|
||||||
|
# Configure grid weights for proper expansion
|
||||||
|
self.product_win.columnconfigure(0, weight=1)
|
||||||
|
self.product_win.rowconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# Header frame
|
||||||
|
header_frame = ttk.Frame(self.product_win, padding="10")
|
||||||
|
header_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=(10, 5))
|
||||||
|
header_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
ttk.Label(header_frame, text="Produkt für Barcode auswählen:",
|
||||||
|
style='Heading.TLabel').grid(row=0, column=0, sticky="w")
|
||||||
|
ttk.Label(header_frame, text=f"'{barcode_data}'",
|
||||||
|
style='Info.TLabel', font=('Courier', 10)).grid(row=1, column=0, sticky="w", pady=(0, 5))
|
||||||
|
|
||||||
|
# Main content frame with scrollable area
|
||||||
|
content_frame = ttk.Frame(self.product_win)
|
||||||
|
content_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)
|
||||||
|
content_frame.columnconfigure(0, weight=1)
|
||||||
|
content_frame.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
selected_product = tk.StringVar()
|
||||||
|
|
||||||
|
# Create a canvas with scrollbar for the product list
|
||||||
|
# Create a canvas with scrollbar for the product list
|
||||||
|
canvas = tk.Canvas(content_frame, bg='white', highlightthickness=0)
|
||||||
|
scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=canvas.yview)
|
||||||
|
canvas.configure(yscrollcommand=scrollbar.set)
|
||||||
|
|
||||||
|
# Frame inside canvas
|
||||||
|
scrollable_frame = ttk.Frame(canvas)
|
||||||
|
window_id = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
||||||
|
|
||||||
|
# Update scrollregion when frame contents change
|
||||||
|
scrollable_frame.bind(
|
||||||
|
"<Configure>",
|
||||||
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Resize inner frame width when canvas resizes
|
||||||
|
def on_canvas_configure(event):
|
||||||
|
canvas.itemconfig(window_id, width=event.width)
|
||||||
|
|
||||||
|
canvas.bind("<Configure>", on_canvas_configure)
|
||||||
|
|
||||||
|
# Place canvas + scrollbar
|
||||||
|
canvas.grid(row=0, column=0, sticky="nsew")
|
||||||
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
||||||
|
|
||||||
|
# Add products to scrollable frame
|
||||||
|
if self.products:
|
||||||
|
for i, product in enumerate(self.products):
|
||||||
|
current_quantity = self.quantities.get(product, 0)
|
||||||
|
price = self.prices.get(product, 0.00)
|
||||||
|
|
||||||
|
product_frame = ttk.Frame(scrollable_frame, padding="5")
|
||||||
|
product_frame.pack(fill="x", pady=2)
|
||||||
|
product_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
ttk.Radiobutton(
|
||||||
|
product_frame,
|
||||||
|
text=product,
|
||||||
|
variable=selected_product,
|
||||||
|
value=product
|
||||||
|
).grid(row=0, column=0, sticky="w")
|
||||||
|
|
||||||
|
info_text = f"Aktuell: {current_quantity}, Preis: €{price:.2f}"
|
||||||
|
ttk.Label(product_frame, text=info_text, style='Info.TLabel').grid(row=0, column=1, sticky="e", padx=(10, 0))
|
||||||
|
else:
|
||||||
|
no_products_label = ttk.Label(scrollable_frame, text="Noch keine Produkte definiert.",
|
||||||
|
style='Info.TLabel', justify="center")
|
||||||
|
no_products_label.pack(pady=20)
|
||||||
|
|
||||||
|
# Button frame at the bottom
|
||||||
|
button_frame = ttk.Frame(self.product_win, padding="10")
|
||||||
|
button_frame.grid(row=2, column=0, sticky="ew", padx=10, pady=(5, 10))
|
||||||
|
|
||||||
|
# Configure button frame columns for proper spacing
|
||||||
|
button_frame.columnconfigure(0, weight=1)
|
||||||
|
button_frame.columnconfigure(1, weight=0)
|
||||||
|
button_frame.columnconfigure(2, weight=0)
|
||||||
|
button_frame.columnconfigure(3, weight=0)
|
||||||
|
|
||||||
|
# Helper functions for buttons
|
||||||
|
def confirm():
|
||||||
|
product = selected_product.get()
|
||||||
|
if product:
|
||||||
|
# Update quantity
|
||||||
|
self.quantities[product] = self.quantities.get(product, 0) + 1
|
||||||
|
|
||||||
|
# Update total value with actual product price
|
||||||
|
product_price = self.prices.get(product, 0.00)
|
||||||
|
self.total_value += product_price
|
||||||
|
|
||||||
|
self.save_json()
|
||||||
|
self.update_statistics()
|
||||||
|
self.product_win.destroy()
|
||||||
|
|
||||||
|
# Show confirmation message
|
||||||
|
messagebox.showinfo("Erfolgreich", f"Produkt '{product}' wurde hinzugefügt!")
|
||||||
|
else:
|
||||||
|
messagebox.showwarning("Keine Auswahl", "Bitte wählen Sie ein Produkt aus.")
|
||||||
|
|
||||||
|
def cancel():
|
||||||
|
self.product_win.destroy()
|
||||||
|
|
||||||
|
def add_new_product():
|
||||||
|
new_product = simpledialog.askstring("Neues Produkt", "Name des neuen Produkts:")
|
||||||
|
if new_product and new_product.strip():
|
||||||
|
new_product = new_product.strip()
|
||||||
|
if new_product not in self.products:
|
||||||
|
# Ask for price
|
||||||
|
price_str = simpledialog.askstring("Preis", f"Preis für '{new_product}' (€):")
|
||||||
|
try:
|
||||||
|
price = float(price_str.replace(',', '.')) if price_str else 0.00
|
||||||
|
except:
|
||||||
|
price = 0.00
|
||||||
|
|
||||||
|
self.products.append(new_product)
|
||||||
|
self.prices[new_product] = price
|
||||||
|
|
||||||
|
# Save products
|
||||||
|
try:
|
||||||
|
products_data = {"products": self.products, "prices": self.prices}
|
||||||
|
with open(self.products_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(products_data, f, indent=4, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Speichern der Produkte: {e}")
|
||||||
|
|
||||||
|
# Refresh the dialog
|
||||||
|
self.product_win.destroy()
|
||||||
|
self.window.after(0, self.show_product_selection, barcode_data)
|
||||||
|
else:
|
||||||
|
messagebox.showwarning("Produkt existiert", "Dieses Produkt ist bereits vorhanden.")
|
||||||
|
|
||||||
|
# Buttons with proper layout
|
||||||
|
ttk.Button(button_frame, text="Neues Produkt", command=add_new_product).grid(row=0, column=0, sticky="w", padx=5)
|
||||||
|
ttk.Button(button_frame, text="Abbrechen", command=cancel).grid(row=0, column=2, padx=5)
|
||||||
|
ttk.Button(button_frame, text="Bestätigen", command=confirm).grid(row=0, column=3, padx=5)
|
||||||
|
|
||||||
|
# Bind Enter key to confirm
|
||||||
|
self.product_win.bind('<Return>', lambda e: confirm())
|
||||||
|
self.product_win.bind('<Escape>', lambda e: cancel())
|
||||||
|
|
||||||
|
# Set focus to the window
|
||||||
|
self.product_win.focus_set()
|
||||||
|
|
||||||
|
def process_queue(self):
|
||||||
|
try:
|
||||||
|
# This method is now handled by the process_worker thread
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler in der Warteschlangenverarbeitung: {e}")
|
||||||
|
|
||||||
|
# Schedule next check
|
||||||
|
if self.running:
|
||||||
self.window.after(100, self.process_queue)
|
self.window.after(100, self.process_queue)
|
||||||
|
|
||||||
def on_closing(self):
|
def on_closing(self):
|
||||||
if self.cap and self.cap.isOpened():
|
self.running = False
|
||||||
|
|
||||||
|
# Wait for camera thread to finish
|
||||||
|
if self.camera_thread and self.camera_thread.is_alive():
|
||||||
|
self.camera_thread.join(timeout=1.0)
|
||||||
|
|
||||||
|
if self.process_thread and self.process_thread.is_alive():
|
||||||
|
self.process_thread.join(timeout=1.0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if hasattr(self, 'cap') and self.cap and self.cap.isOpened():
|
||||||
self.cap.release()
|
self.cap.release()
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
self.window.destroy()
|
self.window.destroy()
|
||||||
|
|
||||||
if __name__ != "__main__":
|
if __name__ != "__main__":
|
||||||
def launch_pfand_scanner():
|
def launch_pfand_scanner():
|
||||||
scanner_window = tk.Toplevel()
|
scanner_window = tk.Toplevel()
|
||||||
PfandScanner(scanner_window, "µScan V2.2.2")
|
PfandScanner(scanner_window, "µScan V2.3.5")
|
||||||
|
else:
|
||||||
|
# For standalone testing
|
||||||
|
root = tk.Tk()
|
||||||
|
app = PfandScanner(root, "µScan V2.3.5")
|
||||||
|
root.mainloop()
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
# #
|
# #
|
||||||
# AUTOGENERATED BY SETUPKEYTOOL V1.0.03 #
|
# AUTOGENERATED BY SETUPKEYTOOL V1.0.03 #
|
||||||
# THIS IS SENSITIVE INFORMATION #
|
# THIS IS SENSITIVE INFORMATION #
|
||||||
|
# #
|
||||||
|
# THIS IS FILLED IN BY THE #
|
||||||
|
# GUI ASSISTANT #
|
||||||
|
# #
|
||||||
# @ZockerKatze/@rattatwinko #
|
# @ZockerKatze/@rattatwinko #
|
||||||
# #
|
# #
|
||||||
#########################################
|
#########################################
|
||||||
@@ -11,4 +15,4 @@ from tgtg import TgtgClient
|
|||||||
# Don't ever give this Info to someone you dont trust!
|
# Don't ever give this Info to someone you dont trust!
|
||||||
# This is the Private Key to your TGTG Account
|
# This is the Private Key to your TGTG Account
|
||||||
|
|
||||||
client = TgtgClient(access_token="asdf", refresh_token="asdf", cookie="asdf")
|
client = TgtgClient(access_token="", refresh_token="", cookie="")
|
||||||
|
|||||||
@@ -61,10 +61,10 @@ def ask_for_tokens():
|
|||||||
|
|
||||||
# Create Tkinter window
|
# Create Tkinter window
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
root.title("Enter API Credentials")
|
root.title("API Kredenzen")
|
||||||
|
|
||||||
|
|
||||||
title_label = tk.Label(root, text="Enter your API Credentials", font=("Arial", 14, "bold"))
|
title_label = tk.Label(root, text="Gebe deine API Kredenzen ein!", font=("Arial", 14, "bold"))
|
||||||
title_label.grid(row=0, columnspan=2, pady=10)
|
title_label.grid(row=0, columnspan=2, pady=10)
|
||||||
|
|
||||||
# Add labels and entry fields for the tokens and cookie
|
# Add labels and entry fields for the tokens and cookie
|
||||||
@@ -81,7 +81,7 @@ def ask_for_tokens():
|
|||||||
cookie_entry.grid(row=3, column=1)
|
cookie_entry.grid(row=3, column=1)
|
||||||
|
|
||||||
# Submit button to process the tokens
|
# Submit button to process the tokens
|
||||||
submit_button = tk.Button(root, text="Submit", command=submit_tokens)
|
submit_button = tk.Button(root, text="Speichern", command=submit_tokens)
|
||||||
submit_button.grid(row=4, columnspan=2)
|
submit_button.grid(row=4, columnspan=2)
|
||||||
|
|
||||||
# Keep the window on top
|
# Keep the window on top
|
||||||
|
|||||||
@@ -1,72 +1,65 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class todo:
|
class todo:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# ------------------------------------------------------------------------------------------------------------------#
|
# ------------------------------------------------------------------------------------------------------------------#
|
||||||
self.todo: str = """
|
self.todo: str = """
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
[X] main.py@433 - fix this to actually load the Todo List cause currently its dumb as fuck.
|
|
||||||
- Do this tmrw 11.JUN.25! easy task. at least it should be.
|
|
||||||
[ ] main.py@GENERAL - refactor to make the ChatGPT Code actually readable.
|
[ ] main.py@GENERAL - refactor to make the ChatGPT Code actually readable.
|
||||||
|
[x] main.py@TODO - Refactor and make it look nicer! PRIO: HIGH
|
||||||
[ ] @Project_Structure - Fix this shit
|
[ ] @Project_Structure - Fix this shit
|
||||||
|
- Isnt too bad , we can work with the structure.
|
||||||
"""
|
"""
|
||||||
# ------------------------------------------------------------------------------------------------------------------#
|
# ------------------------------------------------------------------------------------------------------------------#
|
||||||
self.archived_todo: str = """
|
self.archived_todo: str = """
|
||||||
archived_todo list:
|
archived_todo list:
|
||||||
[X] main.py@433 - fix this to actually load the Todo List cause currently its dumb as fuck.
|
[X] main.py@433 - {FINISHED: 12.JUN.25@21:20} - fix this to actually load the Todo List cause currently its dumb as fuck.
|
||||||
- Do this tmrw 11.JUN.25! easy task. at least it should be.
|
- Do this tmrw 11.JUN.25! easy task. at least it should be.
|
||||||
|
[X] @ImportStructure - {FINISHED: 13.SEP.25@18:47} - Fix the Import structure, it currently looks like ass. At least the local ImportStructure.
|
||||||
|
- @LocalImportStructure => Fix by end of next week. (KW25)
|
||||||
"""
|
"""
|
||||||
# ------------------------------------------------------------------------------------------------------------------#
|
# ------------------------------------------------------------------------------------------------------------------#
|
||||||
self.pineapple = """
|
self.changelog: str = """
|
||||||
⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡿⠛⣷⡄⠀⠀⠀⠀⠀⢀⣀⣤⣤⣶⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
Changelog:
|
||||||
⠀⠀⠀⠀⠀⠘⠻⣿⣿⢶⣤⣀⠀⠀⠀⢀⣾⠟⠀⠀⠘⢿⡆⠀⣠⣴⠿⣻⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
13.SEP.25@18:49 => Fixed µScan.
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠻⣷⡈⠻⣷⣄⠀⣼⡟⠀⠀⠀⠀⠸⣷⣼⠟⠁⣴⠿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
- Fixed the UI, made it look better and made the Scanner run better.
|
||||||
⠀⠀⠀⠀⢰⣤⣄⣀⠀⠘⣿⡀⠈⠻⣷⡿⠀⠀⠀⠀⠀⠀⢹⡇⠀⣸⡟⠀⠀⠀⣀⣠⣤⣶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
- Mainly OpenCV as a C extern
|
||||||
⠀⠀⠀⠀⠀⠉⠻⣿⣟⠿⣿⣿⡄⠀⢸⡇⠀⠀⣤⣿⣦⠀⠸⣿⢰⣿⣤⣴⠿⠛⣻⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
- Tkinter pulls it together.
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠙⢷⣄⠉⠛⢷⣼⡇⠀⣴⠟⠁⢻⣧⡀⣿⡾⠟⠁⠀⣠⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
- Its Threaded, so it runs better.
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣧⠀⠀⠙⢷⣾⡟⠀⠀⠀⢻⣿⠟⠁⠀⢀⣾⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⠀⢀⣤⣀⠀⠀⢻⣧⠀⠀⢸⣿⠀⠀⠀⠀⠀⢿⡆⠀⢀⣾⠋⠀⢐⣠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
Fixed Todo Widget to look better. You can see @main:PfandCalculator/create_todo_list()
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠙⠻⣿⣦⣅⣿⡆⠀⢸⡿⠀⠀⣀⠀⠀⢸⣇⠀⣼⣯⣴⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
- Now has a progressbar which counts the elements in the Todo variable, if you check one off [x]. then it counts as finished and the
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣝⠻⣷⣄⢸⡇⢀⣾⠿⣷⡀⠘⣿⣴⠟⠋⣴⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
progressbar advances! main.py@PfandCalculator/create_todo_list/return_elements(str) -> tuple[int, int, int]
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⣿⣏⠉⠹⣿⣇⣾⠏⠀⠹⣧⣰⡿⠃⢀⣾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
Third returned Element being The percent out of 100, which is done.
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣶⣤⣿⣆⠀⠘⢿⣿⠀⠀⠀⢿⡏⠀⢀⣾⣧⣶⣾⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
12.JUN.25@21:23 => Changed the Todo List to be much more readable.
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⡝⠻⢷⣤⣸⡧⠀⠀⠀⢸⣧⣴⠟⢋⣴⡟⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
12.JUN.25@21:20 => Fixed Todo List Load Bug. Now loads properly. It was a import issue.
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⡄⠀⠙⠿⠷⠀⠀⠀⣿⠟⠁⢠⣾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡶⢿⡿⣿⠿⠿⠿⠿⠿⠿⣿⣷⠿⠾⢿⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⠀⠀⣠⣴⠟⠋⠀⠀⣽⣏⠀⠀⠀⠀⠀⠀⣸⢷⣄⠀⠀⠉⠛⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⢀⣶⠻⡅⠀⠀⣠⡞⠁⠙⢧⠀⠀⠀⠀⡼⠃⠀⠙⢷⣄⠀⣼⠁⠙⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⣰⡿⠁⠀⢿⣄⡾⠋⠀⠀⠀⠈⢳⣄⢀⡾⠁⠀⠀⠀⠀⠙⣷⣏⠀⠀⠈⠻⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⣼⡟⠀⠀⣀⡶⢻⡀⠀⠀⠀⠀⠀⠀⣹⣿⡀⠀⠀⠀⠀⠀⢠⡟⠈⠛⢦⣀⠀⢹⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⣼⣟⠀⣠⡴⠋⠀⠀⠙⣦⠀⠀⠀⢀⡴⠋⠀⠙⢦⡀⠀⠀⣰⠟⠀⠀⠀⠀⠉⠛⣾⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⣰⣿⡽⣟⠁⠀⠀⠀⠀⠀⠈⢷⣄⡴⠋⠀⠀⠀⠀⠀⠙⢦⣴⠋⠀⠀⠀⠀⠀⠀⢰⡏⠀⢹⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⢀⣿⠃⠀⠸⣦⠀⠀⠀⠀⠀⢀⡴⠿⢧⡀⠀⠀⠀⠀⠀⢀⡼⠙⠳⢤⡀⠀⠀⠀⢠⡏⠀⠀⠈⢿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⣸⡟⠀⠀⠀⠘⢧⡀⠀⢀⡴⠋⠀⠀⠈⠹⣦⡀⠀⢀⣴⠏⠁⠀⠀⠀⠙⠲⢤⣠⡏⠀⠀⠀⠀⠸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⣿⠃⠀⠀⠀⠀⢈⣿⣾⠋⠀⠀⠀⠀⠀⠀⠀⠛⣦⡞⠁⠀⠀⠀⠀⠀⠀⠀⣤⠟⠓⠦⣤⣀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⣿⠀⠀⣀⡤⠖⠋⠀⠙⢷⡀⠀⠀⠀⠀⠀⣠⠞⠁⠙⠳⣤⡀⠀⠀⠀⢀⡼⠋⠀⠀⠀⠀⠈⠙⣳⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⣿⠷⣏⠉⠀⠀⠀⠀⠀⠀⠙⢦⡀⢀⣰⠞⠁⠀⠀⠀⠀⠈⠙⠶⢤⣠⠟⠀⠀⠀⠀⠀⠀⠀⣰⠏⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⣿⠀⠹⣆⠀⠀⠀⠀⠀⠀⢀⣠⠟⢯⣄⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠉⠓⢶⣄⡀⠀⠀⠀⡼⠃⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⣿⡆⠀⠈⢳⡄⠀⣀⣤⠞⠋⠁⠀⠀⠉⠳⣄⡀⠀⠀⠀⣠⡞⠃⠀⠀⠀⠀⠀⠉⠛⢳⣾⣥⣀⡀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⢸⣷⣀⣀⡤⠾⢿⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢳⣤⣞⠃⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⢹⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⣿⡏⠁⠀⠀⠀⠙⢶⣀⠀⠀⠀⠀⠀⢀⣠⠶⠋⠁⠉⠳⠦⣄⣀⠀⠀⠀⣠⠟⠀⠀⠀⠀⢀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠹⣷⡄⠀⠀⠀⠀⠀⠉⠳⣦⣀⣤⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠉⢙⣶⢾⣥⣀⠀⠀⠀⠀⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠹⣧⡀⠀⠀⠀⣠⣤⠖⠛⠙⠳⣤⡀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠈⠙⠛⢳⣾⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠹⣷⣶⡛⠉⠁⠀⠀⠀⠀⠀⠀⠉⠳⠦⣄⡀⣀⡴⠞⠉⠀⠀⠀⠀⠀⠀⢀⡴⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠙⢿⣙⢦⣄⡀⠀⠀⠀⠀⠀⠀⢀⣀⡼⠛⠛⠶⢤⣀⣀⠀⠀⠀⣀⡴⢋⣼⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⠈⠻⣦⡈⠙⢦⣄⣀⣠⡴⠚⠋⠁⠀⠀⠀⠀⠀⠈⠉⢛⡶⠾⣧⣴⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠈⠻⣷⣾⡛⠙⠳⢦⣄⡀⠀⠀⠀⠀⢀⣠⡴⠚⢉⣠⣴⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⠷⣦⣄⣀⣉⣻⣶⢴⣞⣋⣠⣴⡶⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠛⠛⠛⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------------------------------------------#
|
# ------------------------------------------------------------------------------------------------------------------#
|
||||||
|
self.tldr : str = """
|
||||||
|
Quick TLDR:
|
||||||
|
If you see any "ref" in the main.py file you are probably looking at a reference to a certain file.
|
||||||
|
example: ref@todo/main.py , element int(x)
|
||||||
|
This is refering to the todo list with element of X
|
||||||
|
|
||||||
# This Project is certainly older than mucapy , But still i declare types now. in todo code xD
|
This can be for other things too, such as other Methods in Classes.
|
||||||
# Anyways. This function maybe needs fixing. due too toodoo bug (bug1)
|
example: ref@tgtg_orderchecker/main.py@method/class/variable
|
||||||
|
This is refering to some object.
|
||||||
|
|
||||||
|
if you see any "@" , you are probably seeing a reference to the todo list.
|
||||||
|
for example element 3 in todo (@LocalImportStructure)
|
||||||
|
"""
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------#
|
||||||
|
|
||||||
|
# Methods to return the things we want.
|
||||||
def load_todo(self) -> str:
|
def load_todo(self) -> str:
|
||||||
return self.todo
|
return self.todo
|
||||||
|
|
||||||
|
def load_changelog(self) -> str:
|
||||||
|
return self.changelog
|
||||||
|
|
||||||
|
def load_tldr(self) -> str:
|
||||||
|
return self.tldr
|
||||||
|
|
||||||
# toodoo bug may also reside here. idk
|
# toodoo bug may also reside here. idk
|
||||||
todo_instance = todo()
|
todo_instance = todo()
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import tempfile
|
|||||||
import traceback
|
import traceback
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
GITHUB_REPO_ZIP = "https://github.com/ZockerKatze/pfand_PKG/archive/refs/heads/main.zip"
|
GITHUB_REPO_ZIP = "https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/pfand_PKG/archive/main.zip"
|
||||||
IGNORED_FILES = {"key.py"}
|
IGNORED_FILES = {"key.py"}
|
||||||
|
|
||||||
class GitHubUpdater(tk.Toplevel):
|
class GitHubUpdater(tk.Toplevel):
|
||||||
@@ -49,7 +49,7 @@ class GitHubUpdater(tk.Toplevel):
|
|||||||
header = ttk.Label(self, text="Pfand Updater", style="Header.TLabel")
|
header = ttk.Label(self, text="Pfand Updater", style="Header.TLabel")
|
||||||
header.pack(pady=(20, 5))
|
header.pack(pady=(20, 5))
|
||||||
|
|
||||||
self.status_label = ttk.Label(self, text="🔍 Suche nach Updates...", style="Status.TLabel")
|
self.status_label = ttk.Label(self, text="Suche nach Updates …", style="Status.TLabel")
|
||||||
self.status_label.pack(pady=(0, 10))
|
self.status_label.pack(pady=(0, 10))
|
||||||
|
|
||||||
self.frame = ttk.Frame(self)
|
self.frame = ttk.Frame(self)
|
||||||
@@ -73,14 +73,14 @@ class GitHubUpdater(tk.Toplevel):
|
|||||||
button_frame = ttk.Frame(self)
|
button_frame = ttk.Frame(self)
|
||||||
button_frame.pack(pady=15)
|
button_frame.pack(pady=15)
|
||||||
|
|
||||||
self.back_button = ttk.Button(button_frame, text="⬅️ Zurück", command=self.show_root_view)
|
self.back_button = ttk.Button(button_frame, text="Zurück", command=self.show_root_view)
|
||||||
self.back_button.pack(side="left", padx=10)
|
self.back_button.pack(side="left", padx=10)
|
||||||
self.back_button.pack_forget()
|
self.back_button.pack_forget()
|
||||||
|
|
||||||
self.update_button = ttk.Button(button_frame, text="⬆️ Dateien aktualisieren", command=self.perform_update, state='disabled')
|
self.update_button = ttk.Button(button_frame, text="Dateien aktualisieren", command=self.perform_update, state='disabled')
|
||||||
self.update_button.pack(side="left", padx=10)
|
self.update_button.pack(side="left", padx=10)
|
||||||
|
|
||||||
self.toggle_debug_btn = ttk.Button(self, text="🐞 Fehlerdetails anzeigen", command=self.toggle_debug_output)
|
self.toggle_debug_btn = ttk.Button(self, text="Fehlerdetails anzeigen", command=self.toggle_debug_output)
|
||||||
self.toggle_debug_btn.pack()
|
self.toggle_debug_btn.pack()
|
||||||
self.toggle_debug_btn.pack_forget()
|
self.toggle_debug_btn.pack_forget()
|
||||||
|
|
||||||
@@ -93,10 +93,10 @@ class GitHubUpdater(tk.Toplevel):
|
|||||||
self.debug_visible = not self.debug_visible
|
self.debug_visible = not self.debug_visible
|
||||||
if self.debug_visible:
|
if self.debug_visible:
|
||||||
self.debug_output.pack()
|
self.debug_output.pack()
|
||||||
self.toggle_debug_btn.config(text="🔽 Fehlerdetails verbergen")
|
self.toggle_debug_btn.config(text="Fehlerdetails verbergen")
|
||||||
else:
|
else:
|
||||||
self.debug_output.pack_forget()
|
self.debug_output.pack_forget()
|
||||||
self.toggle_debug_btn.config(text="🐞 Fehlerdetails anzeigen")
|
self.toggle_debug_btn.config(text="Fehlerdetails anzeigen")
|
||||||
|
|
||||||
def show_root_view(self):
|
def show_root_view(self):
|
||||||
self.current_view = "root"
|
self.current_view = "root"
|
||||||
@@ -125,7 +125,7 @@ class GitHubUpdater(tk.Toplevel):
|
|||||||
|
|
||||||
def check_for_updates(self):
|
def check_for_updates(self):
|
||||||
try:
|
try:
|
||||||
self.status_label.config(text="⬇️ Lade Update herunter...", foreground="#ffb300")
|
self.status_label.config(text="Lade Update herunter...", foreground="#ffb300")
|
||||||
self.update_idletasks()
|
self.update_idletasks()
|
||||||
|
|
||||||
response = requests.get(GITHUB_REPO_ZIP)
|
response = requests.get(GITHUB_REPO_ZIP)
|
||||||
@@ -137,13 +137,13 @@ class GitHubUpdater(tk.Toplevel):
|
|||||||
|
|
||||||
if self.file_differences:
|
if self.file_differences:
|
||||||
self.structure = self.build_structure(self.file_differences)
|
self.structure = self.build_structure(self.file_differences)
|
||||||
self.status_label.config(text="⚠️ Updates verfügbar", foreground="#e53935")
|
self.status_label.config(text="Updates verfügbar", foreground="#e53935")
|
||||||
self.display_structure(self.structure)
|
self.display_structure(self.structure)
|
||||||
self.update_button.config(state='normal')
|
self.update_button.config(state='normal')
|
||||||
else:
|
else:
|
||||||
self.status_label.config(text="✅ Alles ist aktuell", foreground="#43a047")
|
self.status_label.config(text="Alles ist aktuell", foreground="#43a047")
|
||||||
except Exception:
|
except Exception:
|
||||||
self.status_label.config(text="❌ Fehler beim Laden", foreground="#e53935")
|
self.status_label.config(text="Fehler beim Laden", foreground="#e53935")
|
||||||
self.toggle_debug_btn.pack()
|
self.toggle_debug_btn.pack()
|
||||||
self.debug_output.insert("1.0", traceback.format_exc())
|
self.debug_output.insert("1.0", traceback.format_exc())
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ class GitHubUpdater(tk.Toplevel):
|
|||||||
|
|
||||||
def perform_update(self):
|
def perform_update(self):
|
||||||
self.update_button.config(state='disabled')
|
self.update_button.config(state='disabled')
|
||||||
self.status_label.config(text="🚧 Update läuft...", foreground="#fb8c00")
|
self.status_label.config(text="Update läuft…", foreground="#fb8c00")
|
||||||
self.update_idletasks()
|
self.update_idletasks()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -199,10 +199,10 @@ class GitHubUpdater(tk.Toplevel):
|
|||||||
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
||||||
shutil.copy2(src_path, dest_path)
|
shutil.copy2(src_path, dest_path)
|
||||||
|
|
||||||
messagebox.showinfo("✅ Aktualisiert", "Dateien wurden erfolgreich aktualisiert.")
|
messagebox.showinfo("Aktualisiert", "Dateien wurden erfolgreich aktualisiert.")
|
||||||
self.destroy()
|
self.destroy()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("❌ Fehler", str(e))
|
messagebox.showerror("Fehler", str(e))
|
||||||
self.toggle_debug_btn.pack()
|
self.toggle_debug_btn.pack()
|
||||||
self.debug_output.insert("1.0", traceback.format_exc())
|
self.debug_output.insert("1.0", traceback.format_exc())
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ def run_silent_update(master=None):
|
|||||||
file_differences.append(rel_path)
|
file_differences.append(rel_path)
|
||||||
|
|
||||||
if file_differences:
|
if file_differences:
|
||||||
result = messagebox.askyesno("🔄 Update verfügbar", "Es sind Updates verfügbar. Möchten Sie aktualisieren?")
|
result = messagebox.askyesno("Update verfügbar", "Es sind Updates verfügbar. Möchten Sie aktualisieren?")
|
||||||
if result:
|
if result:
|
||||||
updater = GitHubUpdater(master)
|
updater = GitHubUpdater(master)
|
||||||
updater.grab_set()
|
updater.grab_set()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"products": ["Flaschen", "Bierflasche", "Kasten", "Dose", "Plastikflasche", "Monster", "Joghurt Glas"], "prices": {"Flaschen": 0.25, "Bierflasche": 0.2, "Kasten": 3.0, "Dose": 0.25, "Plastikflasche": 0.25, "Monster": 0.25, "Joghurt Glas": 0.17}}
|
{"products": ["Flaschen", "Bierflasche", "Kasten", "Dose", "Plastikflasche", "Joghurt Glas"], "prices": {"Flaschen": 0.25, "Bierflasche": 0.2, "Kasten": 3.0, "Dose": 0.25, "Plastikflasche": 0.25, "Joghurt Glas": 0.17}}
|
||||||
Reference in New Issue
Block a user