Compare commits
56 Commits
v2.65.1
...
v2.65.2.30
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c7886463d | |||
| bc23f380db | |||
| b9da617009 | |||
| dc38f33536 | |||
| cdc189676b | |||
| 665f797d51 | |||
| e119eeb14a | |||
| d8e6cc76c9 | |||
| bfd5bbb881 | |||
| c64991fb59 | |||
| ce5dca9818 | |||
| 38376046d0 | |||
| 38356acd37 | |||
| 64a11aaa6f | |||
| 1063a1ab49 | |||
| 959467028c | |||
| 9b4c523aa6 | |||
| 411ef933d1 | |||
| 0a1c177442 | |||
| a03c2e4091 | |||
| 564a68e5cb | |||
| aec20c1f5a | |||
| 8800533c8a | |||
| 1db6b3372c | |||
| 169b041677 | |||
| 3a5a9c6e09 | |||
| eadf18ec00 | |||
| b70833ea3e | |||
| f12a0fe237 | |||
| de7f6a0523 | |||
| fa6dc76d10 | |||
| f03ffa85d8 | |||
| 8d6ae56f33 | |||
| 64e339fb46 | |||
| 29ee808337 | |||
| 72d65e6118 | |||
| 135fdd8812 | |||
| 6bb0938be0 | |||
| 8a37ee810a | |||
| 4d1320161a | |||
| b5d6211ae0 | |||
| fa05cd625f | |||
| d18ebb6dc0 | |||
| f3a89157ca | |||
| 766d45dd17 | |||
| 457bbc0dc3 | |||
| 4ddf1733e4 | |||
| ca2a80cc85 | |||
|
|
c7074f499f | ||
|
|
f6f3a69be4 | ||
|
|
23e09729f7 | ||
|
|
0aca05704f | ||
|
|
b08894e984 | ||
|
|
a1bc8742c9 | ||
|
|
6472cabc05 | ||
|
|
56bb8217d3 |
@@ -32,7 +32,7 @@ for:
|
||||
- sh: export PYFA_VERSION="$(python3 -B scripts/dump_version.py)"
|
||||
- sh: mkdir build
|
||||
# Download packaging tool
|
||||
- sh: curl -o $APPIMAGE_TOOL -L https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
|
||||
- sh: curl --fail-with-body -o $APPIMAGE_TOOL -L https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
|
||||
- sh: chmod +x $APPIMAGE_TOOL
|
||||
build_script:
|
||||
- sh: mkdir -p AppDir/opt/pyfa
|
||||
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -33,4 +33,4 @@ pyfa.py text eol=lf
|
||||
*.jpg binary
|
||||
*.icns binary
|
||||
*.ico binary
|
||||
|
||||
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -127,3 +127,4 @@ gitversion
|
||||
|
||||
# vscode settings
|
||||
.vscode
|
||||
eve.db
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "Pyfa-Mod"]
|
||||
path = Pyfa-Mod
|
||||
url = https://github.com/Eivonz/Pyfa-Mod
|
||||
32
build.sh
Normal file
32
build.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building pyfa binary..."
|
||||
|
||||
# Ensure we're using the local venv
|
||||
if [ ! -d ".venv" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
uv venv
|
||||
fi
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing dependencies..."
|
||||
uv pip install -r requirements.txt
|
||||
uv pip install pyinstaller
|
||||
|
||||
# Clean previous builds
|
||||
echo "Cleaning previous builds..."
|
||||
rm -rf build dist
|
||||
|
||||
# Build the binary
|
||||
echo "Building binary with PyInstaller..."
|
||||
uv run pyinstaller pyfa.spec
|
||||
|
||||
# Headless CLI exe (console) into main dist folder
|
||||
if [ -f dist/pyfa_headless/pyfa-headless.exe ]; then
|
||||
cp dist/pyfa_headless/pyfa-headless.exe dist/pyfa/
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Build complete! dist/pyfa/pyfa.exe (GUI), dist/pyfa/pyfa-headless.exe (HTTP server POST /simulate :9123)"
|
||||
294
eos/effects.py
294
eos/effects.py
@@ -1358,10 +1358,11 @@ class Effect485(BaseEffect):
|
||||
Implants named like: Halcyon G Booster (5 of 5)
|
||||
Implants named like: Halcyon R Booster (5 of 5)
|
||||
Implants named like: Inherent Implants 'Squire' Capacitor Systems Operation EO (6 of 6)
|
||||
Implants named like: Rapture Booster (5 of 5)
|
||||
Implants named like: Wightstorm Rapture Booster (4 of 4)
|
||||
Implants named like: grade Rapture (15 of 18)
|
||||
Modules named like: Capacitor Control Circuit (8 of 8)
|
||||
Implant: AIR Overclocker Booster III
|
||||
Implant: AIR Rapture Booster II
|
||||
Implant: Basic Capsuleer Engineering Augmentation Chip
|
||||
Implant: Genolution Core Augmentation CA-2
|
||||
Implant: Quafe Zero Green Apple
|
||||
@@ -2603,15 +2604,22 @@ class Effect891(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Variations of ship: Raven (3 of 4)
|
||||
Module: Anhinga Tertiary Mode
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Cruise Missiles'),
|
||||
'maxVelocity', ship.getModifiedItemAttr('shipBonusCB3'),
|
||||
skill='Caldari Battleship', **kwargs)
|
||||
if 'ship' in context:
|
||||
skill = 'Caldari Battleship'
|
||||
penalties = False
|
||||
else:
|
||||
skill = None
|
||||
penalties = True
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Cruise Missiles'), 'maxVelocity',
|
||||
ship.getModifiedItemAttr('shipBonusCB3'), skill=skill, stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect892(BaseEffect):
|
||||
@@ -2620,15 +2628,22 @@ class Effect892(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Variations of ship: Raven (3 of 4)
|
||||
Module: Anhinga Tertiary Mode
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Torpedoes'),
|
||||
'maxVelocity', ship.getModifiedItemAttr('shipBonusCB3'),
|
||||
skill='Caldari Battleship', **kwargs)
|
||||
if 'ship' in context:
|
||||
skill = 'Caldari Battleship'
|
||||
penalties = False
|
||||
else:
|
||||
skill = None
|
||||
penalties = True
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Torpedoes'), 'maxVelocity',
|
||||
ship.getModifiedItemAttr('shipBonusCB3'), skill=skill, stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect896(BaseEffect):
|
||||
@@ -3285,6 +3300,7 @@ class Effect1024(BaseEffect):
|
||||
shipMissileHeavyVelocityBonusCC2
|
||||
|
||||
Used by:
|
||||
Module: Anhinga Tertiary Mode
|
||||
Ship: Caracal
|
||||
Ship: Osprey Navy Issue
|
||||
"""
|
||||
@@ -3293,9 +3309,15 @@ class Effect1024(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Heavy Missiles'),
|
||||
'maxVelocity', ship.getModifiedItemAttr('shipBonusCC2'),
|
||||
skill='Caldari Cruiser', **kwargs)
|
||||
if 'ship' in context:
|
||||
skill = 'Caldari Cruiser'
|
||||
penalties = False
|
||||
else:
|
||||
skill = None
|
||||
penalties = True
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Heavy Missiles'), 'maxVelocity',
|
||||
ship.getModifiedItemAttr('shipBonusCC2'), skill=skill, stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect1030(BaseEffect):
|
||||
@@ -3905,6 +3927,7 @@ class Effect1230(BaseEffect):
|
||||
shipMissileVelocityPirateFactionFrigate
|
||||
|
||||
Used by:
|
||||
Module: Anhinga Primary Mode
|
||||
Ship: Barghest
|
||||
Ship: Garmur
|
||||
Ship: Laelaps
|
||||
@@ -3916,8 +3939,10 @@ class Effect1230(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
|
||||
'maxVelocity', ship.getModifiedItemAttr('shipBonusRole7'), **kwargs)
|
||||
penalties = 'ship' not in context
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'), 'maxVelocity',
|
||||
ship.getModifiedItemAttr('shipBonusRole7'), stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect1232(BaseEffect):
|
||||
@@ -5654,6 +5679,7 @@ class Effect1885(BaseEffect):
|
||||
shipCruiseLauncherROFBonus2CB
|
||||
|
||||
Used by:
|
||||
Modules named like: Anhinga Mode (3 of 3)
|
||||
Ship: Raven
|
||||
Ship: Raven State Issue
|
||||
"""
|
||||
@@ -5662,9 +5688,18 @@ class Effect1885(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Cruise',
|
||||
'speed', ship.getModifiedItemAttr('shipBonus2CB'),
|
||||
skill='Caldari Battleship', **kwargs)
|
||||
if 'ship' in context:
|
||||
skill = 'Caldari Battleship'
|
||||
penalties = False
|
||||
penaltyGroup = None
|
||||
else:
|
||||
skill = None
|
||||
penalties = True
|
||||
penaltyGroup = 'postPerc'
|
||||
fit.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.group.name == 'Missile Launcher Cruise', 'speed',
|
||||
ship.getModifiedItemAttr('shipBonus2CB'), skill=skill,
|
||||
stackingPenalties=penalties, penaltyGroup=penaltyGroup, **kwargs)
|
||||
|
||||
|
||||
class Effect1886(BaseEffect):
|
||||
@@ -5672,6 +5707,7 @@ class Effect1886(BaseEffect):
|
||||
shipSiegeLauncherROFBonus2CB
|
||||
|
||||
Used by:
|
||||
Modules named like: Anhinga Mode (3 of 3)
|
||||
Ship: Raven
|
||||
Ship: Raven State Issue
|
||||
"""
|
||||
@@ -5680,9 +5716,18 @@ class Effect1886(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Torpedo',
|
||||
'speed', ship.getModifiedItemAttr('shipBonus2CB'),
|
||||
skill='Caldari Battleship', **kwargs)
|
||||
if 'ship' in context:
|
||||
skill = 'Caldari Battleship'
|
||||
penalties = False
|
||||
penaltyGroup = None
|
||||
else:
|
||||
skill = None
|
||||
penalties = True
|
||||
penaltyGroup = 'postPerc'
|
||||
fit.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.group.name == 'Missile Launcher Torpedo', 'speed',
|
||||
ship.getModifiedItemAttr('shipBonus2CB'), skill=skill,
|
||||
stackingPenalties=penalties, penaltyGroup=penaltyGroup, **kwargs)
|
||||
|
||||
|
||||
class Effect1910(BaseEffect):
|
||||
@@ -18182,6 +18227,7 @@ class Effect5213(BaseEffect):
|
||||
shipRocketMaxVelocityBonusRookie
|
||||
|
||||
Used by:
|
||||
Module: Skua Sharpshooter Mode
|
||||
Ship: Taipan
|
||||
"""
|
||||
|
||||
@@ -18189,8 +18235,10 @@ class Effect5213(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Rockets'),
|
||||
'maxVelocity', ship.getModifiedItemAttr('rookieRocketVelocity'), **kwargs)
|
||||
penalties = 'ship' not in context
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Rockets'), 'maxVelocity',
|
||||
ship.getModifiedItemAttr('rookieRocketVelocity'), stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect5214(BaseEffect):
|
||||
@@ -18198,6 +18246,7 @@ class Effect5214(BaseEffect):
|
||||
shipLightMissileMaxVelocityBonusRookie
|
||||
|
||||
Used by:
|
||||
Module: Skua Sharpshooter Mode
|
||||
Ship: Taipan
|
||||
"""
|
||||
|
||||
@@ -18205,8 +18254,10 @@ class Effect5214(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Light Missiles'),
|
||||
'maxVelocity', ship.getModifiedItemAttr('rookieLightMissileVelocity'), **kwargs)
|
||||
penalties = 'ship' not in context
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Light Missiles'), 'maxVelocity',
|
||||
ship.getModifiedItemAttr('rookieLightMissileVelocity'), stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect5215(BaseEffect):
|
||||
@@ -20348,6 +20399,7 @@ class Effect5468(BaseEffect):
|
||||
shipBonusAgilityCI2
|
||||
|
||||
Used by:
|
||||
Module: Anhinga Tertiary Mode
|
||||
Ship: Badger
|
||||
"""
|
||||
|
||||
@@ -20355,7 +20407,14 @@ class Effect5468(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.ship.boostItemAttr('agility', ship.getModifiedItemAttr('shipBonusCI2'), skill='Caldari Hauler', **kwargs)
|
||||
if 'ship' in context:
|
||||
skill = 'Caldari Hauler'
|
||||
penalties = False
|
||||
else:
|
||||
skill = None
|
||||
penalties = True
|
||||
fit.ship.boostItemAttr('agility', ship.getModifiedItemAttr('shipBonusCI2'),
|
||||
skill=skill, stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect5469(BaseEffect):
|
||||
@@ -20888,6 +20947,24 @@ class Effect5559(BaseEffect):
|
||||
'shieldBonus', ship.getModifiedItemAttr('shipBonusMC2'), skill='Minmatar Cruiser', **kwargs)
|
||||
|
||||
|
||||
class Effect5560(BaseEffect):
|
||||
"""
|
||||
roleBonusMarauderMJDRReactivationDelayBonus
|
||||
|
||||
Used by:
|
||||
Module: Anhinga Tertiary Mode
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
penalties = 'ship' not in context
|
||||
fit.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.group.name == 'Micro Jump Drive', 'moduleReactivationDelay',
|
||||
ship.getModifiedItemAttr('roleBonusMarauder'), stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect5564(BaseEffect):
|
||||
"""
|
||||
subSystemBonusCaldariOffensiveCommandBursts
|
||||
@@ -21078,6 +21155,7 @@ class Effect5618(BaseEffect):
|
||||
shipBonusRHMLROF2CB
|
||||
|
||||
Used by:
|
||||
Modules named like: Anhinga Mode (3 of 3)
|
||||
Ship: Raven
|
||||
Ship: Widow
|
||||
"""
|
||||
@@ -21086,8 +21164,18 @@ class Effect5618(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Rapid Heavy',
|
||||
'speed', ship.getModifiedItemAttr('shipBonus2CB'), skill='Caldari Battleship', **kwargs)
|
||||
if 'ship' in context:
|
||||
skill = 'Caldari Battleship'
|
||||
penalties = False
|
||||
penaltyGroup = None
|
||||
else:
|
||||
skill = None
|
||||
penalties = True
|
||||
penaltyGroup = 'postPerc'
|
||||
fit.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.group.name == 'Missile Launcher Rapid Heavy', 'speed',
|
||||
ship.getModifiedItemAttr('shipBonus2CB'), skill=skill,
|
||||
stackingPenalties=penalties, penaltyGroup=penaltyGroup, **kwargs)
|
||||
|
||||
|
||||
class Effect5619(BaseEffect):
|
||||
@@ -22376,6 +22464,8 @@ class Effect5867(BaseEffect):
|
||||
shipBonusMissileExplosionDelayPirateFaction2
|
||||
|
||||
Used by:
|
||||
Module: Anhinga Primary Mode
|
||||
Module: Anhinga Secondary Mode
|
||||
Ship: Barghest
|
||||
Ship: Garmur
|
||||
Ship: Laelaps
|
||||
@@ -22387,8 +22477,10 @@ class Effect5867(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
|
||||
'explosionDelay', ship.getModifiedItemAttr('shipBonusRole8'), **kwargs)
|
||||
penalties = 'ship' not in context
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'), 'explosionDelay',
|
||||
ship.getModifiedItemAttr('shipBonusRole8'), stackingPenalties=penalties, **kwargs)
|
||||
|
||||
|
||||
class Effect5868(BaseEffect):
|
||||
@@ -23310,7 +23402,7 @@ class Effect6009(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ships from group: Strategic Cruiser (4 of 4)
|
||||
Ships from group: Tactical Destroyer (4 of 4)
|
||||
Ships from group: Tactical Destroyer (5 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23327,7 +23419,8 @@ class Effect6010(BaseEffect):
|
||||
shipModeMaxTargetRangePostDiv
|
||||
|
||||
Used by:
|
||||
Modules named like: Sharpshooter Mode (4 of 4)
|
||||
Modules named like: Sharpshooter Mode (5 of 5)
|
||||
Module: Anhinga Primary Mode
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23368,7 +23461,7 @@ class Effect6012(BaseEffect):
|
||||
shipModeScanStrengthPostDiv
|
||||
|
||||
Used by:
|
||||
Modules named like: Sharpshooter Mode (4 of 4)
|
||||
Modules named like: Sharpshooter Mode (5 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23389,8 +23482,7 @@ class Effect6014(BaseEffect):
|
||||
modeSigRadiusPostDiv
|
||||
|
||||
Used by:
|
||||
Module: Confessor Defense Mode
|
||||
Module: Jackdaw Defense Mode
|
||||
Modules named like: Defense Mode (3 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23406,7 +23498,7 @@ class Effect6015(BaseEffect):
|
||||
modeArmorResonancePostDiv
|
||||
|
||||
Used by:
|
||||
Modules named like: Defense Mode (3 of 4)
|
||||
Modules named like: Defense Mode (3 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23432,7 +23524,7 @@ class Effect6016(BaseEffect):
|
||||
modeAgilityPostDiv
|
||||
|
||||
Used by:
|
||||
Modules named like: Propulsion Mode (4 of 4)
|
||||
Modules named like: Propulsion Mode (5 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23646,8 +23738,7 @@ class Effect6041(BaseEffect):
|
||||
modeShieldResonancePostDiv
|
||||
|
||||
Used by:
|
||||
Module: Jackdaw Defense Mode
|
||||
Module: Svipul Defense Mode
|
||||
Modules named like: Defense Mode (3 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23971,6 +24062,7 @@ class Effect6077(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ship: Jackdaw
|
||||
Ship: Skua
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23989,6 +24081,7 @@ class Effect6083(BaseEffect):
|
||||
Used by:
|
||||
Ship: Jackdaw
|
||||
Ship: Metamorphosis
|
||||
Ship: Skua
|
||||
Ship: Sunesis
|
||||
"""
|
||||
|
||||
@@ -24008,6 +24101,7 @@ class Effect6085(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ship: Jackdaw
|
||||
Ship: Skua
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -24082,6 +24176,7 @@ class Effect6098(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ship: Jackdaw
|
||||
Ship: Skua
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -25475,6 +25570,7 @@ class Effect6316(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ships from group: Command Destroyer (3 of 6)
|
||||
Ship: Skua
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -25493,6 +25589,7 @@ class Effect6317(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ships from group: Command Destroyer (6 of 6)
|
||||
Ship: Skua
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -25766,6 +25863,7 @@ class Effect6334(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ships from group: Command Destroyer (3 of 6)
|
||||
Ship: Skua
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -31730,6 +31828,7 @@ class Effect6799(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Module: Jackdaw Sharpshooter Mode
|
||||
Module: Skua Sharpshooter Mode
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -31750,7 +31849,7 @@ class Effect6800(BaseEffect):
|
||||
modeDampTDResistsPostDiv
|
||||
|
||||
Used by:
|
||||
Modules named like: Sharpshooter Mode (4 of 4)
|
||||
Modules named like: Sharpshooter Mode (5 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -31766,8 +31865,7 @@ class Effect6801(BaseEffect):
|
||||
modeMWDandABBoostPostDiv
|
||||
|
||||
Used by:
|
||||
Module: Confessor Propulsion Mode
|
||||
Module: Svipul Propulsion Mode
|
||||
Modules named like: Propulsion Mode (3 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -34763,11 +34861,20 @@ class Effect7117(BaseEffect):
|
||||
roleBonusWarpSpeed
|
||||
|
||||
Used by:
|
||||
Items from category: Ship (42 of 410)
|
||||
Ships from group: Blockade Runner (5 of 5)
|
||||
Ships from group: Covert Ops (9 of 9)
|
||||
Ships from group: Hauler (5 of 18)
|
||||
Ships from group: Interceptor (10 of 10)
|
||||
Ships from group: Interdictor (4 of 4)
|
||||
Ship: Azariel
|
||||
Ship: Cynabal
|
||||
Ship: Dramiel
|
||||
Ship: Khizriel
|
||||
Ship: Leopard
|
||||
Ship: Machariel
|
||||
Ship: Mekubal
|
||||
Ship: Sarathiel
|
||||
Ship: Victorieux Luxury Yacht
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -42398,6 +42505,26 @@ class Effect12757(BaseEffect):
|
||||
'miningCritBonusYield', src.getModifiedItemAttr('miningCritBonusYieldBonus') * src.level, **kwargs)
|
||||
|
||||
|
||||
class Effect12758(BaseEffect):
|
||||
"""
|
||||
shipRoleBonusAnhingaLargeMissilePowerFittingBonus
|
||||
|
||||
Used by:
|
||||
Ship: Anhinga
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemMultiply(
|
||||
lambda mod: mod.item.group.name in (
|
||||
'Missile Launcher Rapid Heavy',
|
||||
'Missile Launcher Cruise',
|
||||
'Missile Launcher Torpedo'),
|
||||
'power', ship.getModifiedItemAttr('AnhingaLargeMissilePowerFittingBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect12759(BaseEffect):
|
||||
"""
|
||||
miningCritChanceBonusOreIceOnline
|
||||
@@ -42452,6 +42579,58 @@ class Effect12761(BaseEffect):
|
||||
stackingPenalties=True, **kwargs)
|
||||
|
||||
|
||||
class Effect12764(BaseEffect):
|
||||
"""
|
||||
shipRoleBonusAnhingaLargeMissileCpuFittingBonus
|
||||
|
||||
Used by:
|
||||
Ship: Anhinga
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemMultiply(
|
||||
lambda mod: mod.item.group.name in (
|
||||
'Missile Launcher Rapid Heavy',
|
||||
'Missile Launcher Cruise',
|
||||
'Missile Launcher Torpedo'),
|
||||
'cpu', ship.getModifiedItemAttr('AnhingaLargeMissileCpuFittingBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect12766(BaseEffect):
|
||||
"""
|
||||
shipBonusTorpedoAndCruiseMissileExplosionRadiusCBC1
|
||||
|
||||
Used by:
|
||||
Ship: Anhinga
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Torpedoes') or mod.charge.requiresSkill('Cruise Missiles'),
|
||||
'aoeCloudSize', ship.getModifiedItemAttr('shipBonusCBC1'), skill='Caldari Battlecruiser', **kwargs)
|
||||
|
||||
|
||||
class Effect12767(BaseEffect):
|
||||
"""
|
||||
tacticalBonusSkuaDefensiveShieldRechargeRate
|
||||
|
||||
Used by:
|
||||
Module: Skua Defense Mode
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, module, context, projectionRange, **kwargs):
|
||||
fit.ship.multiplyItemAttr('shieldRechargeRate', 1 / module.getModifiedItemAttr('modeShieldRechargePostDiv'), **kwargs)
|
||||
|
||||
|
||||
class Effect12771(BaseEffect):
|
||||
"""
|
||||
shipRoleBonusPerseveranceIceMiningCriticalHitChanceBonus
|
||||
@@ -42518,3 +42697,38 @@ class Effect12774(BaseEffect):
|
||||
fit.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill('Ice Harvesting'), 'maxRange',
|
||||
ship.getModifiedItemAttr('shipBonusOreDestroyer3'), skill='Mining Destroyer', **kwargs)
|
||||
|
||||
|
||||
class Effect12777(BaseEffect):
|
||||
"""
|
||||
roleBonusCDLinksPGCPUReductionSkua
|
||||
|
||||
Used by:
|
||||
Ship: Skua
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, src, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Leadership'), 'cpu',
|
||||
src.getModifiedItemAttr('roleBonusCD'), **kwargs)
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Leadership'), 'power',
|
||||
src.getModifiedItemAttr('roleBonusCD'), **kwargs)
|
||||
|
||||
|
||||
class Effect12790(BaseEffect):
|
||||
"""
|
||||
shipBonusTorpedoAndCruiseMissileExplosionVelocityCBC2
|
||||
|
||||
Used by:
|
||||
Ship: Anhinga
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Torpedoes') or mod.charge.requiresSkill('Cruise Missiles'),
|
||||
'aoeVelocity', ship.getModifiedItemAttr('shipBonusCBC2'), skill='Caldari Battlecruiser', **kwargs)
|
||||
|
||||
@@ -126,7 +126,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
valid Item objects, not the Mode objects. Returns None if not a
|
||||
t3 dessy
|
||||
"""
|
||||
if self.item.group.name != "Tactical Destroyer":
|
||||
if self.item.group.name != "Tactical Destroyer" and self.item.name != "Anhinga":
|
||||
return None
|
||||
|
||||
items = []
|
||||
|
||||
@@ -18,13 +18,14 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from . import fitDamageStats
|
||||
from . import fitEwarStats
|
||||
from . import fitRemoteReps
|
||||
from . import fitShieldRegen
|
||||
from . import fitCapacitor
|
||||
from . import fitMobility
|
||||
from . import fitWarpTime
|
||||
from . import fitLockTime
|
||||
from . import fitDamageStats as fitDamageStats
|
||||
from . import fitEwarStats as fitEwarStats
|
||||
from . import fitRemoteReps as fitRemoteReps
|
||||
from . import fitShieldRegen as fitShieldRegen
|
||||
from . import fitCapacitor as fitCapacitor
|
||||
from . import fitMobility as fitMobility
|
||||
from . import fitWarpTime as fitWarpTime
|
||||
from . import fitLockTime as fitLockTime
|
||||
from . import fitHeat as fitHeat
|
||||
# Hidden graphs, available via ctrl-alt-g
|
||||
from . import fitEcmBurstScanresDamps
|
||||
from . import fitEcmBurstScanresDamps as fitEcmBurstScanresDamps
|
||||
|
||||
@@ -332,3 +332,78 @@ class Distance2TpStrGetter(SmoothPointGetter):
|
||||
strMult = calculateMultiplier(strMults)
|
||||
strength = (strMult - 1) * 100
|
||||
return strength
|
||||
|
||||
|
||||
class Distance2JamChanceGetter(SmoothPointGetter):
|
||||
|
||||
_baseResolution = 50
|
||||
_extraDepth = 2
|
||||
|
||||
ECM_ATTRS_GENERAL = ('scanGravimetricStrengthBonus', 'scanLadarStrengthBonus', 'scanMagnetometricStrengthBonus', 'scanRadarStrengthBonus')
|
||||
ECM_ATTRS_FIGHTERS = ('fighterAbilityECMStrengthGravimetric', 'fighterAbilityECMStrengthLadar', 'fighterAbilityECMStrengthMagnetometric', 'fighterAbilityECMStrengthRadar')
|
||||
SCAN_TYPES = ('Gravimetric', 'Ladar', 'Magnetometric', 'Radar')
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
ecms = []
|
||||
for mod in src.item.activeModulesIter():
|
||||
for effectName in ('remoteECMFalloff', 'structureModuleEffectECM'):
|
||||
if effectName in mod.item.effects:
|
||||
ecms.append((
|
||||
tuple(mod.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_GENERAL),
|
||||
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||
if 'doomsdayAOEECM' in mod.item.effects:
|
||||
ecms.append((
|
||||
tuple(mod.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_GENERAL),
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
|
||||
mod.falloff or 0, False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'entityECMFalloff' in drone.item.effects:
|
||||
ecms.extend(drone.amountActive * ((
|
||||
tuple(drone.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_GENERAL),
|
||||
math.inf, 0, True, True),))
|
||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||
if ability.effect.name == 'fighterAbilityECM':
|
||||
ecms.append((
|
||||
tuple(fighter.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_FIGHTERS),
|
||||
math.inf, 0, True, False))
|
||||
# Determine target's strongest sensor type if target is available
|
||||
targetScanTypeIndex = None
|
||||
if tgt is not None:
|
||||
maxStr = -1
|
||||
for i, scanType in enumerate(self.SCAN_TYPES):
|
||||
currStr = tgt.item.ship.getModifiedItemAttr('scan%sStrength' % scanType) or 0
|
||||
if currStr > maxStr:
|
||||
maxStr = currStr
|
||||
targetScanTypeIndex = i
|
||||
return {'ecms': ecms, 'targetScanTypeIndex': targetScanTypeIndex}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
jamStrengths = []
|
||||
targetScanTypeIndex = commonData['targetScanTypeIndex']
|
||||
for strengths, optimal, falloff, needsLock, needsDcr in commonData['ecms']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
rangeFactor = calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
# Use the strength matching the target's sensor type
|
||||
if targetScanTypeIndex is not None and targetScanTypeIndex < len(strengths):
|
||||
strength = strengths[targetScanTypeIndex]
|
||||
effectiveStrength = strength * rangeFactor
|
||||
if effectiveStrength > 0:
|
||||
jamStrengths.append(effectiveStrength)
|
||||
if not jamStrengths:
|
||||
return 0
|
||||
# Get sensor strength from target
|
||||
if tgt is None:
|
||||
return 0
|
||||
sensorStrength = max([tgt.item.ship.getModifiedItemAttr('scan%sStrength' % scanType)
|
||||
for scanType in self.SCAN_TYPES]) or 0
|
||||
if sensorStrength <= 0:
|
||||
return 100 # If target has no sensor strength, 100% jam chance
|
||||
# Calculate jam chance: 1 - (1 - (ecmStrength / sensorStrength)) ^ numJammers
|
||||
retainLockChance = 1
|
||||
for jamStrength in jamStrengths:
|
||||
retainLockChance *= 1 - min(1, jamStrength / sensorStrength)
|
||||
return (1 - retainLockChance) * 100
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
import wx
|
||||
|
||||
from graphs.data.base import FitGraph, Input, XDef, YDef
|
||||
from .getter import (Distance2DampStrLockRangeGetter, Distance2EcmStrMaxGetter, Distance2GdStrRangeGetter, Distance2NeutingStrGetter, Distance2TdStrOptimalGetter,
|
||||
Distance2TpStrGetter, Distance2WebbingStrGetter)
|
||||
from .getter import (Distance2DampStrLockRangeGetter, Distance2EcmStrMaxGetter, Distance2GdStrRangeGetter, Distance2JamChanceGetter, Distance2NeutingStrGetter,
|
||||
Distance2TdStrOptimalGetter, Distance2TpStrGetter, Distance2WebbingStrGetter)
|
||||
|
||||
_t = wx.GetTranslation
|
||||
|
||||
@@ -31,11 +31,13 @@ class FitEwarStatsGraph(FitGraph):
|
||||
# UI stuff
|
||||
internalName = 'ewarStatsGraph'
|
||||
name = _t('Electronic Warfare Stats')
|
||||
hasTargets = True
|
||||
xDefs = [XDef(handle='distance', unit='km', label=_t('Distance'), mainInput=('distance', 'km'))]
|
||||
yDefs = [
|
||||
YDef(handle='neutStr', unit=None, label=_t('Cap neutralized per second'), selectorLabel=_t('Neuts: cap per second')),
|
||||
YDef(handle='webStr', unit='%', label=_t('Speed reduction'), selectorLabel=_t('Webs: speed reduction')),
|
||||
YDef(handle='ecmStrMax', unit=None, label=_t('Combined ECM strength'), selectorLabel=_t('ECM: combined strength')),
|
||||
YDef(handle='jamChance', unit='%', label=_t('Jam chance'), selectorLabel=_t('ECM: jam chance')),
|
||||
YDef(handle='dampStrLockRange', unit='%', label=_t('Lock range reduction'), selectorLabel=_t('Damps: lock range reduction')),
|
||||
YDef(handle='tdStrOptimal', unit='%', label=_t('Turret optimal range reduction'), selectorLabel=_t('TDs: turret optimal range reduction')),
|
||||
YDef(handle='gdStrRange', unit='%', label=_t('Missile flight range reduction'), selectorLabel=_t('GDs: missile flight range reduction')),
|
||||
@@ -53,6 +55,7 @@ class FitEwarStatsGraph(FitGraph):
|
||||
('distance', 'neutStr'): Distance2NeutingStrGetter,
|
||||
('distance', 'webStr'): Distance2WebbingStrGetter,
|
||||
('distance', 'ecmStrMax'): Distance2EcmStrMaxGetter,
|
||||
('distance', 'jamChance'): Distance2JamChanceGetter,
|
||||
('distance', 'dampStrLockRange'): Distance2DampStrLockRangeGetter,
|
||||
('distance', 'tdStrOptimal'): Distance2TdStrOptimalGetter,
|
||||
('distance', 'gdStrRange'): Distance2GdStrRangeGetter,
|
||||
|
||||
25
graphs/data/fitHeat/__init__.py
Normal file
25
graphs/data/fitHeat/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2026
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from .graph import FitHeatGraph
|
||||
|
||||
|
||||
FitHeatGraph.register()
|
||||
|
||||
295
graphs/data/fitHeat/calc.py
Normal file
295
graphs/data/fitHeat/calc.py
Normal file
@@ -0,0 +1,295 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2026
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
import random
|
||||
|
||||
from eos.const import FittingModuleState, FittingSlot
|
||||
|
||||
|
||||
_RACK_SUFFIXES = {
|
||||
FittingSlot.HIGH: "Hi",
|
||||
FittingSlot.MED: "Med",
|
||||
FittingSlot.LOW: "Low",
|
||||
}
|
||||
|
||||
# Cache: (fit_id, rack_slot, max_time_s, iterations) -> list of burnout time samples
|
||||
_burnout_samples_cache = {}
|
||||
|
||||
|
||||
def clear_burnout_samples_cache(fit_id=None):
|
||||
if fit_id is None:
|
||||
_burnout_samples_cache.clear()
|
||||
return
|
||||
to_drop = [k for k in _burnout_samples_cache if k[0] == fit_id]
|
||||
for k in to_drop:
|
||||
del _burnout_samples_cache[k]
|
||||
|
||||
|
||||
def _get_rack_suffix(rack_slot):
|
||||
return _RACK_SUFFIXES[rack_slot]
|
||||
|
||||
|
||||
def iter_rack_modules(fit, rack_slot):
|
||||
for mod in fit.modules:
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
if mod.slot == rack_slot:
|
||||
yield mod
|
||||
|
||||
|
||||
def get_rack_heat_value(fit, rack_slot, time_s):
|
||||
"""
|
||||
Deterministic rack heat H(t) for a given rack and time, in [0, 1].
|
||||
"""
|
||||
rack_suffix = _get_rack_suffix(rack_slot)
|
||||
ship = fit.ship
|
||||
heat_capacity = ship.getModifiedItemAttr(f"heatCapacity{rack_suffix}")
|
||||
heat_generation_multiplier = ship.getModifiedItemAttr("heatGenerationMultiplier")
|
||||
if heat_capacity is None or heat_generation_multiplier is None:
|
||||
raise ValueError("Missing heat attributes on ship for rack heat calculation")
|
||||
# Sum heat absorption over all overheated modules in this rack
|
||||
sum_absorption = 0.0
|
||||
for mod in iter_rack_modules(fit, rack_slot):
|
||||
if mod.state >= FittingModuleState.OVERHEATED:
|
||||
sum_absorption += mod.getModifiedItemAttr("heatAbsorbtionRateModifier")
|
||||
argument = -time_s * heat_generation_multiplier * sum_absorption
|
||||
# Guard against numeric issues
|
||||
try:
|
||||
exp_term = math.exp(argument)
|
||||
except OverflowError:
|
||||
exp_term = 0.0 if argument < 0 else float("inf")
|
||||
heat = heat_capacity / 100.0 - exp_term
|
||||
return heat
|
||||
|
||||
|
||||
def _count_online_modules_by_rack(fit):
|
||||
counts = {
|
||||
FittingSlot.HIGH: 0,
|
||||
FittingSlot.MED: 0,
|
||||
FittingSlot.LOW: 0,
|
||||
}
|
||||
for mod in fit.modules:
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
if mod.state >= FittingModuleState.ONLINE and mod.slot in counts:
|
||||
counts[mod.slot] += 1
|
||||
return counts
|
||||
|
||||
|
||||
def _get_total_slot_count(fit):
|
||||
total = 0
|
||||
for slot_type in (FittingSlot.HIGH, FittingSlot.MED, FittingSlot.LOW, FittingSlot.RIG):
|
||||
total += fit.getNumSlots(slot_type)
|
||||
return total
|
||||
|
||||
|
||||
def _get_base_module_hp(mod):
|
||||
hp = mod.getModifiedItemAttr("hp")
|
||||
return float(hp)
|
||||
|
||||
|
||||
def _get_heat_damage(mod):
|
||||
dmg = mod.getModifiedItemAttr("heatDamage")
|
||||
return float(dmg)
|
||||
|
||||
|
||||
def _get_cycle_time_s(mod):
|
||||
cycle_params = mod.getCycleParameters()
|
||||
if cycle_params is None:
|
||||
return None
|
||||
avg_time_ms = cycle_params.averageTime
|
||||
if not math.isfinite(avg_time_ms) or avg_time_ms <= 0:
|
||||
return None
|
||||
return avg_time_ms / 1000.0
|
||||
|
||||
|
||||
def has_burnout_samples(fit, rack_slot, max_time_s, iterations):
|
||||
cache_key = (getattr(fit, "ID", None), int(rack_slot), max_time_s, iterations)
|
||||
return cache_key in _burnout_samples_cache
|
||||
|
||||
|
||||
def get_first_burnout_samples(fit, rack_slot, max_time_s, iterations, progress_cb=None):
|
||||
"""
|
||||
Monte Carlo simulation of time until the first module in the given rack burns out.
|
||||
Returns a list of burnout times (seconds). If no burnout happens before max_time_s,
|
||||
the sample is set to max_time_s for that run.
|
||||
"""
|
||||
if max_time_s <= 0 or iterations <= 0:
|
||||
raise ValueError("max_time_s and iterations must be positive.")
|
||||
|
||||
cache_key = (getattr(fit, "ID", None), int(rack_slot), max_time_s, iterations)
|
||||
if cache_key in _burnout_samples_cache:
|
||||
return list(_burnout_samples_cache[cache_key])
|
||||
|
||||
rack_suffix = _get_rack_suffix(rack_slot)
|
||||
ship = fit.ship
|
||||
heat_capacity = ship.getModifiedItemAttr(f"heatCapacity{rack_suffix}")
|
||||
heat_generation_multiplier = ship.getModifiedItemAttr("heatGenerationMultiplier")
|
||||
heat_attenuation = ship.getModifiedItemAttr(f"heatAttenuation{rack_suffix}")
|
||||
if (
|
||||
heat_capacity is None
|
||||
or heat_generation_multiplier is None
|
||||
or heat_generation_multiplier <= 0
|
||||
or heat_attenuation is None
|
||||
):
|
||||
raise ValueError("Missing heat attributes on ship for burnout simulation")
|
||||
|
||||
rack_modules = list(iter_rack_modules(fit, rack_slot))
|
||||
if not rack_modules:
|
||||
raise ValueError("No modules in this rack.")
|
||||
|
||||
overheated_indices = [
|
||||
idx for idx, mod in enumerate(rack_modules) if mod.state >= FittingModuleState.OVERHEATED
|
||||
]
|
||||
if not overheated_indices:
|
||||
raise ValueError(
|
||||
"No overheated modules in this rack. Overheat at least one module in this rack to see the first-burnout CDF."
|
||||
)
|
||||
|
||||
total_slots = _get_total_slot_count(fit)
|
||||
if total_slots <= 0:
|
||||
raise ValueError("Ship has no high/mid/low/rig slots.")
|
||||
base_online_counts = _count_online_modules_by_rack(fit)
|
||||
|
||||
base_hp = [_get_base_module_hp(mod) for mod in rack_modules]
|
||||
heat_damage = [_get_heat_damage(mod) for mod in rack_modules]
|
||||
heat_absorption = [
|
||||
mod.getModifiedItemAttr("heatAbsorbtionRateModifier") for mod in rack_modules
|
||||
]
|
||||
cycle_times = [_get_cycle_time_s(mod) if idx in overheated_indices else None
|
||||
for idx, mod in enumerate(rack_modules)]
|
||||
eligible_targets = [
|
||||
mod.state >= FittingModuleState.ONLINE for mod in rack_modules
|
||||
]
|
||||
positions = list(range(len(rack_modules)))
|
||||
|
||||
samples = []
|
||||
|
||||
for i in range(iterations):
|
||||
hp = list(base_hp)
|
||||
dead = [hp_val <= 0 for hp_val in hp]
|
||||
online_counts = dict(base_online_counts)
|
||||
next_times = [None] * len(rack_modules)
|
||||
|
||||
for idx in overheated_indices:
|
||||
if not dead[idx] and cycle_times[idx] is not None:
|
||||
next_times[idx] = cycle_times[idx]
|
||||
|
||||
sample_time = max_time_s
|
||||
|
||||
while True:
|
||||
# Find next event time
|
||||
candidates = [t for t in next_times if t is not None]
|
||||
if not candidates:
|
||||
break
|
||||
current_time = min(candidates)
|
||||
if current_time > max_time_s:
|
||||
break
|
||||
|
||||
# Dynamic sum of heat absorption from still-active overheated modules
|
||||
sum_absorption = 0.0
|
||||
for idx in overheated_indices:
|
||||
if not dead[idx] and cycle_times[idx] is not None:
|
||||
sum_absorption += heat_absorption[idx]
|
||||
if sum_absorption <= 0:
|
||||
break
|
||||
|
||||
argument = -current_time * heat_generation_multiplier * sum_absorption
|
||||
try:
|
||||
exp_term = math.exp(argument)
|
||||
except OverflowError:
|
||||
exp_term = 0.0 if argument < 0 else float("inf")
|
||||
heat = heat_capacity / 100.0 - exp_term
|
||||
if heat <= 0:
|
||||
break
|
||||
|
||||
numerator = (
|
||||
online_counts[FittingSlot.HIGH]
|
||||
+ online_counts[FittingSlot.MED]
|
||||
+ online_counts[FittingSlot.LOW]
|
||||
)
|
||||
slot_factor = numerator / float(total_slots)
|
||||
if slot_factor <= 0:
|
||||
break
|
||||
|
||||
# Sources that complete a cycle at this time
|
||||
event_sources = [
|
||||
idx
|
||||
for idx in overheated_indices
|
||||
if not dead[idx]
|
||||
and next_times[idx] is not None
|
||||
and abs(next_times[idx] - current_time) <= 1e-9
|
||||
]
|
||||
if not event_sources:
|
||||
# No actual events despite candidates, advance all timers and continue
|
||||
for idx, next_time in enumerate(next_times):
|
||||
if next_time is not None and cycle_times[idx] is not None:
|
||||
next_times[idx] = next_time + cycle_times[idx]
|
||||
continue
|
||||
|
||||
burn_time = None
|
||||
|
||||
for src_idx in event_sources:
|
||||
dmg = heat_damage[src_idx]
|
||||
if dmg <= 0:
|
||||
continue
|
||||
src_pos = positions[src_idx]
|
||||
for tgt_idx, tgt_hp in enumerate(hp):
|
||||
if dead[tgt_idx] or not eligible_targets[tgt_idx]:
|
||||
continue
|
||||
distance = abs(positions[tgt_idx] - src_pos)
|
||||
attenuation_factor = heat_attenuation ** distance
|
||||
probability = heat * slot_factor * attenuation_factor
|
||||
if probability <= 0:
|
||||
continue
|
||||
if probability >= 1.0 or random.random() < probability:
|
||||
new_hp = tgt_hp - dmg
|
||||
hp[tgt_idx] = new_hp
|
||||
if new_hp <= 0 and not dead[tgt_idx]:
|
||||
dead[tgt_idx] = True
|
||||
if rack_modules[tgt_idx].slot in online_counts and rack_modules[
|
||||
tgt_idx
|
||||
].state >= FittingModuleState.ONLINE:
|
||||
online_counts[rack_modules[tgt_idx].slot] -= 1
|
||||
if tgt_idx in overheated_indices:
|
||||
next_times[tgt_idx] = None
|
||||
if burn_time is None or current_time < burn_time:
|
||||
burn_time = current_time
|
||||
|
||||
if burn_time is not None:
|
||||
sample_time = burn_time
|
||||
break
|
||||
|
||||
# Advance timers for all sources that fired at this time
|
||||
for src_idx in event_sources:
|
||||
if not dead[src_idx] and cycle_times[src_idx] is not None:
|
||||
next_times[src_idx] = current_time + cycle_times[src_idx]
|
||||
|
||||
samples.append(sample_time)
|
||||
|
||||
if progress_cb is not None:
|
||||
# progress_cb should return True to continue, False to cancel
|
||||
if not progress_cb(i + 1):
|
||||
break
|
||||
|
||||
_burnout_samples_cache[cache_key] = samples
|
||||
return samples
|
||||
|
||||
159
graphs/data/fitHeat/getter.py
Normal file
159
graphs/data/fitHeat/getter.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2026
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from eos.const import FittingSlot
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
import wx
|
||||
from .calc import get_first_burnout_samples, get_rack_heat_value, has_burnout_samples
|
||||
|
||||
|
||||
class _BaseTime2RackHeatGetter(SmoothPointGetter):
|
||||
|
||||
rack_slot = None
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
return {"fit": src.item}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
fit = commonData["fit"]
|
||||
heat_value = get_rack_heat_value(fit, self.rack_slot, x)
|
||||
return heat_value * 100.0
|
||||
|
||||
|
||||
class Time2RackHeatHiGetter(_BaseTime2RackHeatGetter):
|
||||
rack_slot = FittingSlot.HIGH
|
||||
|
||||
|
||||
class Time2RackHeatMedGetter(_BaseTime2RackHeatGetter):
|
||||
rack_slot = FittingSlot.MED
|
||||
|
||||
|
||||
class Time2RackHeatLowGetter(_BaseTime2RackHeatGetter):
|
||||
rack_slot = FittingSlot.LOW
|
||||
|
||||
|
||||
class _BaseTime2BurnoutCdfGetter(SmoothPointGetter):
|
||||
|
||||
rack_slot = None
|
||||
_iterations = 200
|
||||
|
||||
def getRange(self, xRange, miscParams, src, tgt):
|
||||
fit = src.item
|
||||
# Fixed simulation horizon so CDF does not depend on view range
|
||||
max_sim_time = self.graph._limiters["time"](src, tgt)[1]
|
||||
iterations = miscParams.get("iterations", self._iterations)
|
||||
try:
|
||||
iterations = int(iterations)
|
||||
except (TypeError, ValueError):
|
||||
iterations = self._iterations
|
||||
if iterations <= 0:
|
||||
iterations = self._iterations
|
||||
samples = None
|
||||
# Show a progress dialog only on cache miss for expensive runs
|
||||
if iterations >= 1000 and not has_burnout_samples(fit, self.rack_slot, max_sim_time, iterations):
|
||||
app = wx.GetApp()
|
||||
parent = app.GetTopWindow() if app is not None else None
|
||||
dlg = wx.ProgressDialog(
|
||||
"Computing burnout CDF",
|
||||
"Running overheating simulations...",
|
||||
maximum=iterations,
|
||||
parent=parent,
|
||||
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_AUTO_HIDE,
|
||||
)
|
||||
|
||||
def progress_cb(done):
|
||||
# dlg.Update returns (continue, skip)
|
||||
cont, _ = dlg.Update(done)
|
||||
return cont
|
||||
|
||||
try:
|
||||
samples = get_first_burnout_samples(
|
||||
fit=fit,
|
||||
rack_slot=self.rack_slot,
|
||||
max_time_s=max_sim_time,
|
||||
iterations=iterations,
|
||||
progress_cb=progress_cb,
|
||||
)
|
||||
finally:
|
||||
dlg.Destroy()
|
||||
else:
|
||||
samples = get_first_burnout_samples(
|
||||
fit=fit,
|
||||
rack_slot=self.rack_slot,
|
||||
max_time_s=max_sim_time,
|
||||
iterations=iterations,
|
||||
)
|
||||
xs = []
|
||||
ys = []
|
||||
if not samples:
|
||||
for x in self._xIterLinear(xRange):
|
||||
xs.append(x)
|
||||
ys.append(0.0)
|
||||
return xs, ys
|
||||
samples = sorted(samples)
|
||||
total = float(len(samples))
|
||||
index = 0
|
||||
for x in self._xIterLinear(xRange):
|
||||
while index < len(samples) and samples[index] <= x:
|
||||
index += 1
|
||||
xs.append(x)
|
||||
ys.append(index / total)
|
||||
return xs, ys
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
return self.getPoint(x=x, miscParams=miscParams, src=src, tgt=tgt)
|
||||
|
||||
def getPoint(self, x, miscParams, src, tgt):
|
||||
fit = src.item
|
||||
max_sim_time = self.graph._limiters["time"](src, tgt)[1]
|
||||
iterations = miscParams.get("iterations", self._iterations)
|
||||
try:
|
||||
iterations = int(iterations)
|
||||
except (TypeError, ValueError):
|
||||
iterations = self._iterations
|
||||
if iterations <= 0:
|
||||
iterations = self._iterations
|
||||
samples = get_first_burnout_samples(
|
||||
fit=fit,
|
||||
rack_slot=self.rack_slot,
|
||||
max_time_s=max_sim_time,
|
||||
iterations=iterations,
|
||||
)
|
||||
if not samples:
|
||||
return 0.0
|
||||
samples = sorted(samples)
|
||||
total = float(len(samples))
|
||||
index = 0
|
||||
while index < len(samples) and samples[index] <= x:
|
||||
index += 1
|
||||
return index / total
|
||||
|
||||
|
||||
class Time2BurnoutCdfHiGetter(_BaseTime2BurnoutCdfGetter):
|
||||
rack_slot = FittingSlot.HIGH
|
||||
|
||||
|
||||
class Time2BurnoutCdfMedGetter(_BaseTime2BurnoutCdfGetter):
|
||||
rack_slot = FittingSlot.MED
|
||||
|
||||
|
||||
class Time2BurnoutCdfLowGetter(_BaseTime2BurnoutCdfGetter):
|
||||
rack_slot = FittingSlot.LOW
|
||||
|
||||
104
graphs/data/fitHeat/graph.py
Normal file
104
graphs/data/fitHeat/graph.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2026
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from service.const import GraphCacheCleanupReason
|
||||
from graphs.data.base import FitGraph, Input, XDef, YDef
|
||||
from .getter import (
|
||||
Time2BurnoutCdfHiGetter,
|
||||
Time2BurnoutCdfLowGetter,
|
||||
Time2BurnoutCdfMedGetter,
|
||||
Time2RackHeatHiGetter,
|
||||
Time2RackHeatLowGetter,
|
||||
Time2RackHeatMedGetter,
|
||||
)
|
||||
|
||||
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
_CDF_Y_HANDLES = frozenset(("burnoutCdfHi", "burnoutCdfMed", "burnoutCdfLow"))
|
||||
|
||||
|
||||
class FitHeatGraph(FitGraph):
|
||||
|
||||
def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, src, tgt=None):
|
||||
if ySpec.handle in _CDF_Y_HANDLES:
|
||||
return self._calcPlotPoints(
|
||||
mainInput=mainInput, miscInputs=miscInputs,
|
||||
xSpec=xSpec, ySpec=ySpec, src=src, tgt=tgt)
|
||||
return super().getPlotPoints(
|
||||
mainInput=mainInput, miscInputs=miscInputs,
|
||||
xSpec=xSpec, ySpec=ySpec, src=src, tgt=tgt)
|
||||
|
||||
# UI stuff
|
||||
internalName = "heatGraph"
|
||||
name = _t("Heat")
|
||||
xDefs = [
|
||||
XDef(handle="time", unit="s", label=_t("Time"), mainInput=("time", "s")),
|
||||
]
|
||||
yDefs = [
|
||||
YDef(handle="burnoutCdfHi", unit=None, label=_t("High rack first-burnout CDF")),
|
||||
YDef(handle="burnoutCdfMed", unit=None, label=_t("Mid rack first-burnout CDF")),
|
||||
YDef(handle="burnoutCdfLow", unit=None, label=_t("Low rack first-burnout CDF")),
|
||||
YDef(handle="rackHeatHi", unit="%", label=_t("High rack heat")),
|
||||
YDef(handle="rackHeatMed", unit="%", label=_t("Mid rack heat")),
|
||||
YDef(handle="rackHeatLow", unit="%", label=_t("Low rack heat")),
|
||||
]
|
||||
inputs = [
|
||||
Input(
|
||||
handle="time",
|
||||
unit="s",
|
||||
label=_t("Time"),
|
||||
iconID=1392,
|
||||
defaultValue=300,
|
||||
defaultRange=(0, 120),
|
||||
),
|
||||
Input(
|
||||
handle="iterations",
|
||||
unit=None,
|
||||
label=_t("Iterations"),
|
||||
iconID=1392,
|
||||
defaultValue=10000,
|
||||
defaultRange=(100, 50000),
|
||||
),
|
||||
]
|
||||
srcExtraCols = ()
|
||||
|
||||
# Calculation stuff
|
||||
_limiters = {
|
||||
"time": lambda src, tgt: (0, 3600),
|
||||
}
|
||||
_getters = {
|
||||
("time", "rackHeatHi"): Time2RackHeatHiGetter,
|
||||
("time", "rackHeatMed"): Time2RackHeatMedGetter,
|
||||
("time", "rackHeatLow"): Time2RackHeatLowGetter,
|
||||
("time", "burnoutCdfHi"): Time2BurnoutCdfHiGetter,
|
||||
("time", "burnoutCdfMed"): Time2BurnoutCdfMedGetter,
|
||||
("time", "burnoutCdfLow"): Time2BurnoutCdfLowGetter,
|
||||
}
|
||||
|
||||
def clearCache(self, reason, extraData=None):
|
||||
super().clearCache(reason=reason, extraData=extraData)
|
||||
from .calc import clear_burnout_samples_cache
|
||||
if reason in (GraphCacheCleanupReason.fitChanged, GraphCacheCleanupReason.fitRemoved) and extraData is not None:
|
||||
clear_burnout_samples_cache(fit_id=extraData)
|
||||
@@ -159,11 +159,24 @@ class CargoView(d.Display):
|
||||
else:
|
||||
dstCargoItemID = None
|
||||
|
||||
self.mainFrame.command.Submit(cmd.GuiLocalModuleToCargoCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
modPosition=modIdx,
|
||||
cargoItemID=dstCargoItemID,
|
||||
copy=wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL))
|
||||
modifiers = wx.GetMouseState().GetModifiers()
|
||||
isCopy = modifiers == wx.MOD_CONTROL
|
||||
isBatch = modifiers == wx.MOD_SHIFT
|
||||
if isBatch:
|
||||
self.mainFrame.command.Submit(
|
||||
cmd.GuiBatchLocalModuleToCargoCommand(
|
||||
fitID=self.mainFrame.getActiveFit(), modPosition=modIdx, copy=isCopy
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.mainFrame.command.Submit(
|
||||
cmd.GuiLocalModuleToCargoCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
modPosition=modIdx,
|
||||
cargoItemID=dstCargoItemID,
|
||||
copy=isCopy,
|
||||
)
|
||||
)
|
||||
|
||||
def fitChanged(self, event):
|
||||
event.Skip()
|
||||
|
||||
@@ -18,6 +18,7 @@ from gui.builtinContextMenus import resistMode
|
||||
from gui.builtinContextMenus.targetProfile import editor
|
||||
# Item info
|
||||
from gui.builtinContextMenus import itemStats
|
||||
from gui.builtinContextMenus import fitDiff
|
||||
from gui.builtinContextMenus import itemMarketJump
|
||||
from gui.builtinContextMenus import fitSystemSecurity # Not really an item info but want to keep it here
|
||||
from gui.builtinContextMenus import fitPilotSecurity # Not really an item info but want to keep it here
|
||||
|
||||
@@ -47,6 +47,8 @@ class AddCurrentlyOpenFit(ContextMenuUnconditional):
|
||||
if isinstance(page, BlankPage):
|
||||
continue
|
||||
fit = sFit.getFit(page.activeFitID, basic=True)
|
||||
if fit is None:
|
||||
continue
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, "{}: {}".format(fit.ship.item.name, fit.name))
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
|
||||
|
||||
48
gui/builtinContextMenus/fitDiff.py
Normal file
48
gui/builtinContextMenus/fitDiff.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2025
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
class FitDiff(ContextMenuSingle):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
# Only show for fittingShip context (right-click on ship)
|
||||
return srcContext == "fittingShip"
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem):
|
||||
return _t("Fit Diff...")
|
||||
|
||||
def activate(self, callingWindow, fullContext, mainItem, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is not None:
|
||||
from gui.fitDiffFrame import FitDiffFrame
|
||||
FitDiffFrame(self.mainFrame, fitID)
|
||||
|
||||
|
||||
FitDiff.register()
|
||||
@@ -5,9 +5,6 @@ import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from gui.fitCommands import (
|
||||
GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand,
|
||||
GuiConvertMutatedLocalDroneCommand, GuiRevertMutatedLocalDroneCommand)
|
||||
from service.fit import Fit
|
||||
|
||||
_t = wx.GetTranslation
|
||||
@@ -65,6 +62,8 @@ class ChangeItemMutation(ContextMenuSingle):
|
||||
return sub
|
||||
|
||||
def handleMenu(self, event):
|
||||
from gui.fitCommands import (
|
||||
GuiConvertMutatedLocalModuleCommand, GuiConvertMutatedLocalDroneCommand)
|
||||
mutaplasmid, item = self.eventIDs[event.Id]
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
@@ -78,6 +77,8 @@ class ChangeItemMutation(ContextMenuSingle):
|
||||
fitID=fitID, position=position, mutaplasmid=mutaplasmid))
|
||||
|
||||
def activate(self, callingWindow, fullContext, mainItem, i):
|
||||
from gui.fitCommands import (
|
||||
GuiRevertMutatedLocalModuleCommand, GuiRevertMutatedLocalDroneCommand)
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if mainItem in fit.modules:
|
||||
|
||||
@@ -17,7 +17,10 @@ class ChangeShipTacticalMode(ContextMenuUnconditional):
|
||||
self.modeMap = {
|
||||
'Defense': _t('Defense'),
|
||||
'Propulsion': _t('Propulsion'),
|
||||
'Sharpshooter': _t('Sharpshooter')
|
||||
'Sharpshooter': _t('Sharpshooter'),
|
||||
'Primary': _t('Primary'),
|
||||
'Secondary': _t('Secondary'),
|
||||
'Tertiary': _t('Tertiary'),
|
||||
}
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
|
||||
@@ -79,7 +79,7 @@ class ChangeAffectingSkills(ContextMenuSingle):
|
||||
label = _t("Level %s") % i
|
||||
|
||||
id = ContextMenuSingle.nextID()
|
||||
self.skillIds[id] = (skill, i)
|
||||
self.skillIds[id] = (skill, i, False) # False = not "up" for individual skills
|
||||
menuItem = wx.MenuItem(rootMenu, id, label, kind=wx.ITEM_RADIO)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handleSkillChange, menuItem)
|
||||
return menuItem
|
||||
@@ -89,6 +89,40 @@ class ChangeAffectingSkills(ContextMenuSingle):
|
||||
self.skillIds = {}
|
||||
sub = wx.Menu()
|
||||
|
||||
# When rootMenu is None (direct menu access), use sub for binding on Windows
|
||||
bindMenu = rootMenu if (rootMenu is not None and msw) else (sub if msw else None)
|
||||
|
||||
# Add "All" entry
|
||||
allItem = wx.MenuItem(sub, ContextMenuSingle.nextID(), _t("All"))
|
||||
grandSubAll = wx.Menu()
|
||||
allItem.SetSubMenu(grandSubAll)
|
||||
|
||||
# For "All", only show levels 1-5 (not "Not Learned")
|
||||
for i in range(1, 6):
|
||||
id = ContextMenuSingle.nextID()
|
||||
self.skillIds[id] = (None, i, False) # None indicates "All" was selected, False = not "up"
|
||||
label = _t("Level %s") % i
|
||||
menuItem = wx.MenuItem(bindMenu if bindMenu else grandSubAll, id, label, kind=wx.ITEM_RADIO)
|
||||
grandSubAll.Bind(wx.EVT_MENU, self.handleSkillChange, menuItem)
|
||||
grandSubAll.Append(menuItem)
|
||||
|
||||
# Add separator
|
||||
grandSubAll.AppendSeparator()
|
||||
|
||||
# Add "Up Level 1..5" entries
|
||||
for i in range(1, 6):
|
||||
id = ContextMenuSingle.nextID()
|
||||
self.skillIds[id] = (None, i, True) # None indicates "All" was selected, True = "up" only
|
||||
label = _t("Up Level %s") % i
|
||||
menuItem = wx.MenuItem(bindMenu if bindMenu else grandSubAll, id, label, kind=wx.ITEM_RADIO)
|
||||
grandSubAll.Bind(wx.EVT_MENU, self.handleSkillChange, menuItem)
|
||||
grandSubAll.Append(menuItem)
|
||||
|
||||
sub.Append(allItem)
|
||||
|
||||
# Add separator
|
||||
sub.AppendSeparator()
|
||||
|
||||
for skill in self.skills:
|
||||
skillItem = wx.MenuItem(sub, ContextMenuSingle.nextID(), skill.item.name)
|
||||
grandSub = wx.Menu()
|
||||
@@ -99,7 +133,7 @@ class ChangeAffectingSkills(ContextMenuSingle):
|
||||
skillItem.SetBitmap(bitmap)
|
||||
|
||||
for i in range(-1, 6):
|
||||
levelItem = self.addSkill(rootMenu if msw else grandSub, skill, i)
|
||||
levelItem = self.addSkill(bindMenu if bindMenu else grandSub, skill, i)
|
||||
grandSub.Append(levelItem)
|
||||
if (not skill.learned and i == -1) or (skill.learned and skill.level == i):
|
||||
levelItem.Check(True)
|
||||
@@ -108,9 +142,24 @@ class ChangeAffectingSkills(ContextMenuSingle):
|
||||
return sub
|
||||
|
||||
def handleSkillChange(self, event):
|
||||
skill, level = self.skillIds[event.Id]
|
||||
skill, level, up = self.skillIds[event.Id]
|
||||
|
||||
if skill is None: # "All" was selected
|
||||
for s in self.skills:
|
||||
if up:
|
||||
# Only increase skill if it's below the target level
|
||||
if not s.learned or s.level < level:
|
||||
self.sChar.changeLevel(self.charID, s.item.ID, level)
|
||||
else:
|
||||
self.sChar.changeLevel(self.charID, s.item.ID, level)
|
||||
else:
|
||||
if up:
|
||||
# Only increase skill if it's below the target level
|
||||
if not skill.learned or skill.level < level:
|
||||
self.sChar.changeLevel(self.charID, skill.item.ID, level)
|
||||
else:
|
||||
self.sChar.changeLevel(self.charID, skill.item.ID, level)
|
||||
|
||||
self.sChar.changeLevel(self.charID, skill.item.ID, level)
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.sFit.changeChar(fitID, self.charID)
|
||||
|
||||
|
||||
@@ -9,6 +9,46 @@ from gui.utils.numberFormatter import formatAmount
|
||||
|
||||
_t = wx.GetTranslation
|
||||
|
||||
# Mapping of repair/transfer amount attributes to their duration attribute and display name
|
||||
PER_SECOND_ATTRIBUTES = {
|
||||
"armorDamageAmount": {
|
||||
"durationAttr": "duration",
|
||||
"displayName": "Armor Hitpoints Repaired per second",
|
||||
"unit": "HP/s"
|
||||
},
|
||||
"shieldBonus": {
|
||||
"durationAttr": "duration",
|
||||
"displayName": "Shield Hitpoints Repaired per second",
|
||||
"unit": "HP/s"
|
||||
},
|
||||
"powerTransferAmount": {
|
||||
"durationAttr": "duration",
|
||||
"displayName": "Capacitor Transferred per second",
|
||||
"unit": "GJ/s"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PerSecondAttributeInfo:
|
||||
"""Helper class to store info about computed per-second attributes"""
|
||||
def __init__(self, displayName, unit):
|
||||
self.displayName = displayName
|
||||
self.unit = PerSecondUnit(unit)
|
||||
|
||||
|
||||
class PerSecondUnit:
|
||||
"""Helper class to mimic the Unit class for per-second attributes"""
|
||||
def __init__(self, displayName):
|
||||
self.displayName = displayName
|
||||
self.name = ""
|
||||
|
||||
|
||||
class PerSecondAttributeValue:
|
||||
"""Helper class to store computed per-second attribute values"""
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.info = None # Will be set when adding to attrs
|
||||
|
||||
|
||||
def defaultSort(item):
|
||||
return (item.metaLevel or 0, item.name)
|
||||
@@ -36,8 +76,12 @@ class ItemCompare(wx.Panel):
|
||||
self.item = item
|
||||
self.items = sorted(items, key=defaultSort)
|
||||
self.attrs = {}
|
||||
self.computedAttrs = {} # Store computed per-second attributes
|
||||
self.HighlightOn = wx.Colour(255, 255, 0, wx.ALPHA_OPAQUE)
|
||||
self.highlightedNames = []
|
||||
self.bangBuckColumn = None # Store the column selected for bang/buck calculation
|
||||
self.bangBuckColumnName = None # Store the display name of the selected column
|
||||
self.columnHighlightColour = wx.Colour(173, 216, 230, wx.ALPHA_OPAQUE) # Light blue for column highlight
|
||||
|
||||
# get a dict of attrName: attrInfo of all unique attributes across all items
|
||||
for item in self.items:
|
||||
@@ -45,23 +89,66 @@ class ItemCompare(wx.Panel):
|
||||
if item.attributes[attr].info.displayName:
|
||||
self.attrs[attr] = item.attributes[attr].info
|
||||
|
||||
# Compute per-second attributes for items that have both the amount and duration
|
||||
for perSecondKey, config in PER_SECOND_ATTRIBUTES.items():
|
||||
amountAttr = perSecondKey
|
||||
durationAttr = config["durationAttr"]
|
||||
perSecondAttrName = f"{perSecondKey}_per_second"
|
||||
|
||||
# Check if any item has both attributes
|
||||
hasPerSecondAttr = False
|
||||
for item in self.items:
|
||||
if amountAttr in item.attributes and durationAttr in item.attributes:
|
||||
hasPerSecondAttr = True
|
||||
break
|
||||
|
||||
if hasPerSecondAttr:
|
||||
# Add the per-second attribute info to attrs
|
||||
perSecondInfo = PerSecondAttributeInfo(config["displayName"], config["unit"])
|
||||
self.attrs[perSecondAttrName] = perSecondInfo
|
||||
self.computedAttrs[perSecondAttrName] = {
|
||||
"amountAttr": amountAttr,
|
||||
"durationAttr": durationAttr
|
||||
}
|
||||
|
||||
# Process attributes for items and find ones that differ
|
||||
for attr in list(self.attrs.keys()):
|
||||
value = None
|
||||
|
||||
for item in self.items:
|
||||
# we can automatically break here if this item doesn't have the attribute,
|
||||
# as that means at least one item did
|
||||
if attr not in item.attributes:
|
||||
break
|
||||
# Check if this is a computed attribute
|
||||
if attr in self.computedAttrs:
|
||||
computed = self.computedAttrs[attr]
|
||||
amountAttr = computed["amountAttr"]
|
||||
durationAttr = computed["durationAttr"]
|
||||
|
||||
# this is the first attribute for the item set, set the initial value
|
||||
if value is None:
|
||||
value = item.attributes[attr].value
|
||||
continue
|
||||
# Item needs both attributes to compute per-second value
|
||||
if amountAttr not in item.attributes or durationAttr not in item.attributes:
|
||||
break
|
||||
|
||||
if attr not in item.attributes or item.attributes[attr].value != value:
|
||||
break
|
||||
# Calculate per-second value
|
||||
amountValue = item.attributes[amountAttr].value
|
||||
durationValue = item.attributes[durationAttr].value
|
||||
# Duration is in milliseconds, convert to seconds
|
||||
perSecondValue = amountValue / (durationValue / 1000.0) if durationValue > 0 else 0
|
||||
|
||||
if value is None:
|
||||
value = perSecondValue
|
||||
continue
|
||||
|
||||
if perSecondValue != value:
|
||||
break
|
||||
else:
|
||||
# Regular attribute handling
|
||||
if attr not in item.attributes:
|
||||
break
|
||||
|
||||
if value is None:
|
||||
value = item.attributes[attr].value
|
||||
continue
|
||||
|
||||
if item.attributes[attr].value != value:
|
||||
break
|
||||
else:
|
||||
# attribute values were all the same, delete
|
||||
del self.attrs[attr]
|
||||
@@ -89,6 +176,7 @@ class ItemCompare(wx.Panel):
|
||||
|
||||
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
|
||||
self.Bind(wx.EVT_LIST_COL_CLICK, self.SortCompareCols)
|
||||
self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColumnRightClick)
|
||||
|
||||
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.HighlightRow)
|
||||
|
||||
@@ -105,6 +193,23 @@ class ItemCompare(wx.Panel):
|
||||
self.Thaw()
|
||||
event.Skip()
|
||||
|
||||
def OnColumnRightClick(self, event):
|
||||
column = event.GetColumn()
|
||||
# Column 0 is "Item", column len(self.attrs) + 1 is "Price", len(self.attrs) + 2 is "Buck/bang"
|
||||
# Only allow selecting attribute columns (1 to len(self.attrs))
|
||||
if 1 <= column <= len(self.attrs):
|
||||
# If clicking the same column, deselect it
|
||||
if self.bangBuckColumn == column:
|
||||
self.bangBuckColumn = None
|
||||
self.bangBuckColumnName = None
|
||||
else:
|
||||
self.bangBuckColumn = column
|
||||
# Get the display name of the selected column
|
||||
attr_key = list(self.attrs.keys())[column - 1]
|
||||
self.bangBuckColumnName = self.attrs[attr_key].displayName if self.attrs[attr_key].displayName else attr_key
|
||||
self.UpdateList()
|
||||
event.Skip()
|
||||
|
||||
def SortCompareCols(self, event):
|
||||
self.Freeze()
|
||||
self.paramList.ClearAll()
|
||||
@@ -148,12 +253,32 @@ class ItemCompare(wx.Panel):
|
||||
# Remember to reduce by 1, because the attrs array
|
||||
# starts at 0 while the list has the item name as column 0.
|
||||
attr = str(list(self.attrs.keys())[sort - 1])
|
||||
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
|
||||
# Handle computed attributes for sorting
|
||||
if attr in self.computedAttrs:
|
||||
computed = self.computedAttrs[attr]
|
||||
amountAttr = computed["amountAttr"]
|
||||
durationAttr = computed["durationAttr"]
|
||||
func = lambda _val: (_val.attributes[amountAttr].value / (_val.attributes[durationAttr].value / 1000.0)) if (amountAttr in _val.attributes and durationAttr in _val.attributes and _val.attributes[durationAttr].value > 0) else 0.0
|
||||
else:
|
||||
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
|
||||
# Clicked on a column that's not part of our array (price most likely)
|
||||
except IndexError:
|
||||
# Price
|
||||
if sort == len(self.attrs) + 1:
|
||||
func = lambda i: i.price.price if i.price.price != 0 else float("Inf")
|
||||
# Buck/bang
|
||||
elif sort == len(self.attrs) + 2:
|
||||
if self.bangBuckColumn is not None:
|
||||
attr_key = list(self.attrs.keys())[self.bangBuckColumn - 1]
|
||||
if attr_key in self.computedAttrs:
|
||||
computed = self.computedAttrs[attr_key]
|
||||
amountAttr = computed["amountAttr"]
|
||||
durationAttr = computed["durationAttr"]
|
||||
func = lambda i: (i.price.price / (i.attributes[amountAttr].value / (i.attributes[durationAttr].value / 1000.0)) if (amountAttr in i.attributes and durationAttr in i.attributes and i.attributes[durationAttr].value > 0 and (i.attributes[amountAttr].value / (i.attributes[durationAttr].value / 1000.0)) > 0) else float("Inf"))
|
||||
else:
|
||||
func = lambda i: (i.price.price / i.attributes[attr_key].value if (attr_key in i.attributes and i.attributes[attr_key].value > 0) else float("Inf"))
|
||||
else:
|
||||
func = defaultSort
|
||||
# Something else
|
||||
else:
|
||||
self.sortReverse = False
|
||||
@@ -166,18 +291,49 @@ class ItemCompare(wx.Panel):
|
||||
|
||||
for i, attr in enumerate(self.attrs.keys()):
|
||||
name = self.attrs[attr].displayName if self.attrs[attr].displayName else attr
|
||||
# Add indicator if this column is selected for bang/buck calculation
|
||||
if self.bangBuckColumn == i + 1:
|
||||
name = "► " + name
|
||||
self.paramList.InsertColumn(i + 1, name)
|
||||
self.paramList.SetColumnWidth(i + 1, 120)
|
||||
|
||||
self.paramList.InsertColumn(len(self.attrs) + 1, _t("Price"))
|
||||
self.paramList.SetColumnWidth(len(self.attrs) + 1, 60)
|
||||
|
||||
# Add Buck/bang column header
|
||||
buckBangHeader = _t("Buck/bang")
|
||||
if self.bangBuckColumnName:
|
||||
buckBangHeader = _t("Buck/bang ({})").format(self.bangBuckColumnName)
|
||||
self.paramList.InsertColumn(len(self.attrs) + 2, buckBangHeader)
|
||||
self.paramList.SetColumnWidth(len(self.attrs) + 2, 80)
|
||||
|
||||
toHighlight = []
|
||||
|
||||
for item in self.items:
|
||||
i = self.paramList.InsertItem(self.paramList.GetItemCount(), item.name)
|
||||
for x, attr in enumerate(self.attrs.keys()):
|
||||
if attr in item.attributes:
|
||||
# Handle computed attributes
|
||||
if attr in self.computedAttrs:
|
||||
computed = self.computedAttrs[attr]
|
||||
amountAttr = computed["amountAttr"]
|
||||
durationAttr = computed["durationAttr"]
|
||||
|
||||
# Item needs both attributes to display per-second value
|
||||
if amountAttr in item.attributes and durationAttr in item.attributes:
|
||||
amountValue = item.attributes[amountAttr].value
|
||||
durationValue = item.attributes[durationAttr].value
|
||||
# Duration is in milliseconds, convert to seconds
|
||||
perSecondValue = amountValue / (durationValue / 1000.0) if durationValue > 0 else 0
|
||||
|
||||
info = self.attrs[attr]
|
||||
if self.toggleView == 1:
|
||||
valueUnit = formatAmount(perSecondValue, 3, 0, 0) + " " + info.unit.displayName
|
||||
else:
|
||||
valueUnit = str(perSecondValue)
|
||||
|
||||
self.paramList.SetItem(i, x + 1, valueUnit)
|
||||
# else: leave cell empty
|
||||
elif attr in item.attributes:
|
||||
info = self.attrs[attr]
|
||||
value = item.attributes[attr].value
|
||||
if self.toggleView != 1:
|
||||
@@ -191,6 +347,27 @@ class ItemCompare(wx.Panel):
|
||||
|
||||
# Add prices
|
||||
self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True) if item.price.price else "")
|
||||
|
||||
# Add buck/bang values
|
||||
if self.bangBuckColumn is not None and item.price.price and item.price.price > 0:
|
||||
attr_key = list(self.attrs.keys())[self.bangBuckColumn - 1]
|
||||
if attr_key in self.computedAttrs:
|
||||
computed = self.computedAttrs[attr_key]
|
||||
amountAttr = computed["amountAttr"]
|
||||
durationAttr = computed["durationAttr"]
|
||||
if amountAttr in item.attributes and durationAttr in item.attributes:
|
||||
amountValue = item.attributes[amountAttr].value
|
||||
durationValue = item.attributes[durationAttr].value
|
||||
perSecondValue = amountValue / (durationValue / 1000.0) if durationValue > 0 else 0
|
||||
if perSecondValue > 0:
|
||||
buckBangValue = item.price.price / perSecondValue
|
||||
self.paramList.SetItem(i, len(self.attrs) + 2, formatAmount(buckBangValue, 3, 3, 9, currency=True))
|
||||
elif attr_key in item.attributes:
|
||||
attrValue = item.attributes[attr_key].value
|
||||
if attrValue > 0:
|
||||
buckBangValue = item.price.price / attrValue
|
||||
self.paramList.SetItem(i, len(self.attrs) + 2, formatAmount(buckBangValue, 3, 3, 9, currency=True))
|
||||
|
||||
if item.name in self.highlightedNames:
|
||||
toHighlight.append(i)
|
||||
|
||||
|
||||
220
gui/builtinItemStatsViews/itemSkills.py
Normal file
220
gui/builtinItemStatsViews/itemSkills.py
Normal file
@@ -0,0 +1,220 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import itertools
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.character import Skill
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
from eos.saveddata.module import Module as es_Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from gui.utils.clipboard import toClipboard
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from service.fit import Fit
|
||||
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
class ItemSkills(wx.Panel):
|
||||
def __init__(self, parent, stuff, item):
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.stuff = stuff
|
||||
self.item = item
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
leftPanel = wx.Panel(self)
|
||||
leftSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
leftPanel.SetSizer(leftSizer)
|
||||
|
||||
header = wx.StaticText(leftPanel, wx.ID_ANY, _t("Components"))
|
||||
font = header.GetFont()
|
||||
font.SetWeight(wx.FONTWEIGHT_BOLD)
|
||||
header.SetFont(font)
|
||||
leftSizer.Add(header, 0, wx.ALL, 5)
|
||||
|
||||
self.checkboxes = {}
|
||||
components = [
|
||||
("Ship", "ship"),
|
||||
("Modules", "modules"),
|
||||
("Drones", "drones"),
|
||||
("Fighters", "fighters"),
|
||||
("Cargo", "cargo"),
|
||||
("Implants", "appliedImplants"),
|
||||
("Boosters", "boosters"),
|
||||
("Necessary", "necessary"),
|
||||
]
|
||||
|
||||
for label, key in components:
|
||||
cb = wx.CheckBox(leftPanel, wx.ID_ANY, label)
|
||||
cb.SetValue(True)
|
||||
cb.Bind(wx.EVT_CHECKBOX, self.onCheckboxChange)
|
||||
self.checkboxes[key] = cb
|
||||
leftSizer.Add(cb, 0, wx.ALL, 2)
|
||||
|
||||
leftSizer.AddStretchSpacer()
|
||||
|
||||
mainSizer.Add(leftPanel, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
rightPanel = wx.Panel(self)
|
||||
rightSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
rightPanel.SetSizer(rightSizer)
|
||||
|
||||
headerRight = wx.StaticText(rightPanel, wx.ID_ANY, _t("Skills"))
|
||||
fontRight = headerRight.GetFont()
|
||||
fontRight.SetWeight(wx.FONTWEIGHT_BOLD)
|
||||
headerRight.SetFont(fontRight)
|
||||
rightSizer.Add(headerRight, 0, wx.ALL, 5)
|
||||
|
||||
self.skillsText = wx.TextCtrl(rightPanel, wx.ID_ANY, "", style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP)
|
||||
font = wx.Font(9, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
|
||||
self.skillsText.SetFont(font)
|
||||
rightSizer.Add(self.skillsText, 1, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
mainSizer.Add(rightPanel, 1, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
|
||||
self.nbContainer = parent if isinstance(parent, wx.Notebook) else None
|
||||
if self.nbContainer:
|
||||
self.nbContainer.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.onTabChanged)
|
||||
|
||||
self.updateSkills()
|
||||
|
||||
def onCheckboxChange(self, event):
|
||||
self.updateSkills()
|
||||
self._copyToClipboard()
|
||||
|
||||
def updateSkills(self):
|
||||
fitID = gui.mainFrame.MainFrame.getInstance().getActiveFit()
|
||||
if fitID is None:
|
||||
self.skillsText.SetValue("")
|
||||
self._updateCheckboxStates(None)
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
self.skillsText.SetValue("")
|
||||
self._updateCheckboxStates(None)
|
||||
return
|
||||
|
||||
if not fit.calculated:
|
||||
fit.calculate()
|
||||
|
||||
self._updateCheckboxStates(fit)
|
||||
|
||||
char = fit.character
|
||||
skillsMap = {}
|
||||
|
||||
items = []
|
||||
if self.checkboxes["ship"].GetValue():
|
||||
items.append(fit.ship)
|
||||
if self.checkboxes["modules"].GetValue():
|
||||
items.extend(fit.modules)
|
||||
if self.checkboxes["drones"].GetValue():
|
||||
items.extend(fit.drones)
|
||||
if self.checkboxes["fighters"].GetValue():
|
||||
items.extend(fit.fighters)
|
||||
if self.checkboxes["cargo"].GetValue():
|
||||
items.extend(fit.cargo)
|
||||
if self.checkboxes["appliedImplants"].GetValue():
|
||||
items.extend(fit.appliedImplants)
|
||||
if self.checkboxes["boosters"].GetValue():
|
||||
items.extend(fit.boosters)
|
||||
|
||||
for thing in items:
|
||||
self._collectAffectingSkills(thing, char, skillsMap)
|
||||
|
||||
if self.checkboxes["necessary"].GetValue():
|
||||
self._collectRequiredSkills(items, char, skillsMap)
|
||||
|
||||
skillsList = ""
|
||||
for skillName in sorted(skillsMap):
|
||||
charLevel = skillsMap[skillName]
|
||||
for level in range(1, charLevel + 1):
|
||||
skillsList += "%s %d\n" % (skillName, level)
|
||||
|
||||
self.skillsText.SetValue(skillsList)
|
||||
self._copyToClipboard()
|
||||
|
||||
def _copyToClipboard(self):
|
||||
skillsText = self.skillsText.GetValue()
|
||||
if skillsText:
|
||||
toClipboard(skillsText)
|
||||
|
||||
def onTabChanged(self, event):
|
||||
if self.nbContainer:
|
||||
pageIndex = self.nbContainer.FindPage(self)
|
||||
if pageIndex != -1 and event.GetSelection() == pageIndex:
|
||||
self.updateSkills()
|
||||
event.Skip()
|
||||
|
||||
def _updateCheckboxStates(self, fit):
|
||||
if fit is None:
|
||||
for cb in self.checkboxes.values():
|
||||
cb.Enable(False)
|
||||
return
|
||||
|
||||
self.checkboxes["ship"].Enable(True)
|
||||
self.checkboxes["modules"].Enable(len(fit.modules) > 0)
|
||||
self.checkboxes["drones"].Enable(len(fit.drones) > 0)
|
||||
self.checkboxes["fighters"].Enable(len(fit.fighters) > 0)
|
||||
self.checkboxes["cargo"].Enable(len(fit.cargo) > 0)
|
||||
self.checkboxes["appliedImplants"].Enable(len(fit.appliedImplants) > 0)
|
||||
self.checkboxes["boosters"].Enable(len(fit.boosters) > 0)
|
||||
self.checkboxes["necessary"].Enable(True)
|
||||
|
||||
def _collectAffectingSkills(self, thing, char, skillsMap):
|
||||
for attr in ("item", "charge"):
|
||||
if attr == "charge" and isinstance(thing, es_Fighter):
|
||||
continue
|
||||
subThing = getattr(thing, attr, None)
|
||||
if subThing is None:
|
||||
continue
|
||||
if isinstance(thing, es_Fighter) and attr == "charge":
|
||||
continue
|
||||
|
||||
if attr == "charge":
|
||||
cont = getattr(thing, "chargeModifiedAttributes", None)
|
||||
else:
|
||||
cont = getattr(thing, "itemModifiedAttributes", None)
|
||||
|
||||
if cont is not None:
|
||||
for attrName in cont.iterAfflictions():
|
||||
for fit, afflictors in cont.getAfflictions(attrName).items():
|
||||
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
|
||||
if isinstance(afflictor, Skill) and afflictor.character == char:
|
||||
skillName = afflictor.item.name
|
||||
if skillName not in skillsMap:
|
||||
skillsMap[skillName] = afflictor.level
|
||||
elif skillsMap[skillName] < afflictor.level:
|
||||
skillsMap[skillName] = afflictor.level
|
||||
|
||||
def _collectRequiredSkills(self, items, char, skillsMap):
|
||||
"""Collect required skills from items (necessary to use them)"""
|
||||
for thing in items:
|
||||
for attr in ("item", "charge"):
|
||||
if attr == "charge" and isinstance(thing, es_Fighter):
|
||||
continue
|
||||
subThing = getattr(thing, attr, None)
|
||||
if subThing is None:
|
||||
continue
|
||||
if isinstance(thing, es_Fighter) and attr == "charge":
|
||||
continue
|
||||
|
||||
if hasattr(subThing, "requiredSkills"):
|
||||
for reqSkill, level in subThing.requiredSkills.items():
|
||||
skillName = reqSkill.name
|
||||
charSkill = char.getSkill(reqSkill) if char else None
|
||||
charLevel = charSkill.level if charSkill else 0
|
||||
|
||||
if charLevel > 0:
|
||||
if skillName not in skillsMap:
|
||||
skillsMap[skillName] = charLevel
|
||||
elif skillsMap[skillName] < charLevel:
|
||||
skillsMap[skillName] = charLevel
|
||||
else:
|
||||
if skillName not in skillsMap:
|
||||
skillsMap[skillName] = level
|
||||
elif skillsMap[skillName] < level:
|
||||
skillsMap[skillName] = level
|
||||
@@ -5,6 +5,7 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
import gui.globalEvents as GE
|
||||
from config import slotColourMap, slotColourMapDark
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingSlot
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.display import Display
|
||||
@@ -22,6 +23,7 @@ class ItemView(Display):
|
||||
|
||||
DEFAULT_COLS = ["Base Icon",
|
||||
"Base Name",
|
||||
"Price",
|
||||
"attr:power,,,True",
|
||||
"attr:cpu,,,True"]
|
||||
|
||||
@@ -153,19 +155,22 @@ class ItemView(Display):
|
||||
# skip the event so the other handlers also get called
|
||||
event.Skip()
|
||||
|
||||
if self.marketBrowser.mode != 'charges':
|
||||
return
|
||||
|
||||
activeFitID = self.mainFrame.getActiveFit()
|
||||
# if it was not the active fitting that was changed, do not do anything
|
||||
if activeFitID is not None and activeFitID not in event.fitIDs:
|
||||
return
|
||||
|
||||
items = self.getChargesForActiveFit()
|
||||
# Handle charges mode
|
||||
if self.marketBrowser.mode == 'charges':
|
||||
items = self.getChargesForActiveFit()
|
||||
# update the UI
|
||||
self.updateItemStore(items)
|
||||
self.filterItemStore()
|
||||
return
|
||||
|
||||
# update the UI
|
||||
self.updateItemStore(items)
|
||||
self.filterItemStore()
|
||||
# If "Fits" filter is active, re-filter the current view
|
||||
if self.marketBrowser.getFitsFilter():
|
||||
self.filterItemStore()
|
||||
|
||||
def updateItemStore(self, items):
|
||||
self.unfilteredStore = items
|
||||
@@ -197,13 +202,115 @@ class ItemView(Display):
|
||||
if btn.userSelected:
|
||||
selectedMetas.update(sMkt.META_MAP[btn.metaName])
|
||||
filteredItems = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas)
|
||||
|
||||
# Apply slot/fits filters - works IDENTICALLY to meta buttons (filters CURRENT VIEW only)
|
||||
activeSlotFilters = []
|
||||
fitsFilterActive = False
|
||||
for btn in self.marketBrowser.slotButtons:
|
||||
if btn.userSelected and btn.IsEnabled():
|
||||
if btn.filterType == "fits":
|
||||
fitsFilterActive = True
|
||||
elif btn.filterType == "slot":
|
||||
activeSlotFilters.append(btn.slotType)
|
||||
|
||||
# Apply fits filter
|
||||
if fitsFilterActive:
|
||||
filteredItems = self._filterByFits(filteredItems)
|
||||
|
||||
# Apply slot filters
|
||||
if activeSlotFilters:
|
||||
filteredItems = [item for item in filteredItems if Module.calculateSlot(item) in activeSlotFilters]
|
||||
|
||||
return filteredItems
|
||||
|
||||
def _filterByFits(self, items):
|
||||
"""Filter items by remaining CPU/PG - filters CURRENT VIEW only"""
|
||||
fitId = self.mainFrame.getActiveFit()
|
||||
if fitId is None:
|
||||
return []
|
||||
|
||||
fit = self.sFit.getFit(fitId)
|
||||
|
||||
# Get remaining CPU and power grid
|
||||
cpuOutput = fit.ship.getModifiedItemAttr("cpuOutput")
|
||||
powerOutput = fit.ship.getModifiedItemAttr("powerOutput")
|
||||
cpuUsed = fit.cpuUsed
|
||||
pgUsed = fit.pgUsed
|
||||
cpuRemaining = cpuOutput - cpuUsed
|
||||
pgRemaining = powerOutput - pgUsed
|
||||
|
||||
# Get remaining calibration (for rigs)
|
||||
calibrationCapacity = fit.ship.getModifiedItemAttr("upgradeCapacity")
|
||||
calibrationUsed = fit.calibrationUsed
|
||||
calibrationRemaining = None
|
||||
if calibrationCapacity is not None and calibrationCapacity > 0:
|
||||
calibrationRemaining = calibrationCapacity - calibrationUsed
|
||||
|
||||
fittingItems = []
|
||||
for item in items:
|
||||
# Check if item is a module (has a slot)
|
||||
slot = Module.calculateSlot(item)
|
||||
if slot is None:
|
||||
continue
|
||||
|
||||
# Rigs don't use CPU/power, they use calibration - check rig size and calibration
|
||||
if slot == FittingSlot.RIG:
|
||||
# Check if item can fit on the ship
|
||||
if not fit.canFit(item):
|
||||
continue
|
||||
# Check rig size compatibility with ship
|
||||
shipRigSize = fit.ship.getModifiedItemAttr("rigSize")
|
||||
itemRigSize = item.attributes.get("rigSize")
|
||||
if shipRigSize is not None and itemRigSize is not None:
|
||||
if shipRigSize != itemRigSize.value:
|
||||
continue
|
||||
# Check calibration requirement
|
||||
if calibrationRemaining is not None and calibrationRemaining > 0:
|
||||
itemCalibration = item.attributes.get("upgradeCost")
|
||||
if itemCalibration is not None:
|
||||
itemCalibrationValue = itemCalibration.value
|
||||
if itemCalibrationValue > calibrationRemaining:
|
||||
continue
|
||||
fittingItems.append(item)
|
||||
continue
|
||||
|
||||
# For non-rigs, check CPU and power requirements
|
||||
itemCpu = item.attributes.get("cpu")
|
||||
itemPower = item.attributes.get("power")
|
||||
|
||||
# Skip items without CPU or power (not modules)
|
||||
if itemCpu is None and itemPower is None:
|
||||
continue
|
||||
|
||||
# Check CPU requirement
|
||||
if itemCpu is not None:
|
||||
itemCpuValue = itemCpu.value
|
||||
if itemCpuValue > cpuRemaining:
|
||||
continue
|
||||
|
||||
# Check power requirement
|
||||
if itemPower is not None:
|
||||
itemPowerValue = itemPower.value
|
||||
if itemPowerValue > pgRemaining:
|
||||
continue
|
||||
|
||||
# Check if item can fit on the ship (most expensive check, do last)
|
||||
if not fit.canFit(item):
|
||||
continue
|
||||
|
||||
fittingItems.append(item)
|
||||
|
||||
return fittingItems
|
||||
|
||||
def setToggles(self):
|
||||
metaIDs = set()
|
||||
slotIDs = set()
|
||||
sMkt = self.sMkt
|
||||
for item in self.unfilteredStore:
|
||||
metaIDs.add(sMkt.getMetaGroupIdByItem(item))
|
||||
slot = Module.calculateSlot(item)
|
||||
if slot is not None:
|
||||
slotIDs.add(slot)
|
||||
|
||||
for btn in self.marketBrowser.metaButtons:
|
||||
btn.reset()
|
||||
@@ -213,6 +320,23 @@ class ItemView(Display):
|
||||
else:
|
||||
btn.setMetaAvailable(False)
|
||||
|
||||
# Set toggles for slot/fits buttons
|
||||
for btn in self.marketBrowser.slotButtons:
|
||||
btn.reset()
|
||||
if btn.filterType == "fits":
|
||||
# Fits button is available if there's an active fit
|
||||
fitId = self.mainFrame.getActiveFit()
|
||||
isAvailable = fitId is not None
|
||||
btn.setMetaAvailable(isAvailable)
|
||||
if not isAvailable:
|
||||
btn.setUserSelection(False)
|
||||
elif btn.filterType == "slot":
|
||||
# Slot button is available if items with that slot exist in current view
|
||||
isAvailable = btn.slotType in slotIDs
|
||||
btn.setMetaAvailable(isAvailable)
|
||||
if not isAvailable:
|
||||
btn.setUserSelection(False)
|
||||
|
||||
def scheduleSearch(self, event=None):
|
||||
self.searchTimer.Stop() # Cancel any pending timers
|
||||
search = self.marketBrowser.search.GetLineText(0)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from eos.gamedata import Item
|
||||
from eos.saveddata.cargo import Cargo
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
@@ -53,7 +54,14 @@ class Price(ViewColumn):
|
||||
self.imageId = fittingView.imageList.GetImageIndex("totalPrice_small", "gui")
|
||||
|
||||
def getText(self, stuff):
|
||||
if stuff.item is None or stuff.item.group.name == "Ship Modifiers":
|
||||
if isinstance(stuff, Item):
|
||||
item = stuff
|
||||
else:
|
||||
if not hasattr(stuff, "item") or stuff.item is None:
|
||||
return ""
|
||||
item = stuff.item
|
||||
|
||||
if item.group.name == "Ship Modifiers":
|
||||
return ""
|
||||
|
||||
if hasattr(stuff, "isEmpty"):
|
||||
@@ -63,7 +71,7 @@ class Price(ViewColumn):
|
||||
if isinstance(stuff, Module) and stuff.isMutated:
|
||||
return ""
|
||||
|
||||
priceObj = stuff.item.price
|
||||
priceObj = item.price
|
||||
|
||||
if not priceObj.isValid():
|
||||
return False
|
||||
@@ -79,7 +87,11 @@ class Price(ViewColumn):
|
||||
|
||||
display.SetItem(colItem)
|
||||
|
||||
sPrice.getPrices([mod.item], callback, waitforthread=True)
|
||||
if isinstance(mod, Item):
|
||||
item = mod
|
||||
else:
|
||||
item = mod.item
|
||||
sPrice.getPrices([item], callback, waitforthread=True)
|
||||
|
||||
def getImageId(self, mod):
|
||||
return -1
|
||||
|
||||
@@ -497,11 +497,27 @@ class FittingView(d.Display):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if mod in fit.modules:
|
||||
position = fit.modules.index(mod)
|
||||
self.mainFrame.command.Submit(cmd.GuiCargoToLocalModuleCommand(
|
||||
fitID=fitID,
|
||||
cargoItemID=cargoItemID,
|
||||
modPosition=position,
|
||||
copy=wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL))
|
||||
modifiers = wx.GetMouseState().GetModifiers()
|
||||
isCopy = modifiers == wx.MOD_CONTROL
|
||||
isBatch = modifiers == wx.MOD_SHIFT
|
||||
if isBatch:
|
||||
self.mainFrame.command.Submit(
|
||||
cmd.GuiBatchCargoToLocalModuleCommand(
|
||||
fitID=fitID,
|
||||
cargoItemID=cargoItemID,
|
||||
targetPosition=position,
|
||||
copy=isCopy,
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.mainFrame.command.Submit(
|
||||
cmd.GuiCargoToLocalModuleCommand(
|
||||
fitID=fitID,
|
||||
cargoItemID=cargoItemID,
|
||||
modPosition=position,
|
||||
copy=isCopy,
|
||||
)
|
||||
)
|
||||
|
||||
def swapItems(self, x, y, srcIdx):
|
||||
"""Swap two modules in fitting window"""
|
||||
@@ -668,6 +684,21 @@ class FittingView(d.Display):
|
||||
contexts.append(fullContext)
|
||||
contexts.append(("fittingShip", _t("Ship") if not fit.isStructure else _t("Citadel")))
|
||||
|
||||
# Check if shift is held for direct skills menu access
|
||||
if wx.GetKeyState(wx.WXK_SHIFT):
|
||||
from gui.builtinContextMenus.skillAffectors import ChangeAffectingSkills
|
||||
for fullContext in contexts:
|
||||
srcContext = fullContext[0]
|
||||
itemContext = fullContext[1] if len(fullContext) > 1 else None
|
||||
skillsMenu = ChangeAffectingSkills()
|
||||
if skillsMenu.display(self, srcContext, mainMod):
|
||||
# On Windows, menu items need to be bound to the menu shown with PopupMenu
|
||||
# We pass None as rootMenu so items are bound to their parent submenus
|
||||
sub = skillsMenu.getSubMenu(self, srcContext, mainMod, None, 0, None)
|
||||
if sub:
|
||||
self.PopupMenu(sub)
|
||||
return
|
||||
|
||||
menu = ContextMenu.getMenu(self, mainMod, selection, *contexts)
|
||||
self.PopupMenu(menu)
|
||||
|
||||
@@ -780,7 +811,6 @@ class FittingView(d.Display):
|
||||
del mod.restrictionOverridden
|
||||
hasRestrictionOverriden = not hasRestrictionOverriden
|
||||
|
||||
|
||||
if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red
|
||||
self.SetItemBackgroundColour(i, errColorDark if isDark() else errColor)
|
||||
elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled
|
||||
|
||||
@@ -29,7 +29,7 @@ import config
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.clipboard import toClipboard
|
||||
from gui.utils.clipboard import toClipboard, fromClipboard
|
||||
from service.character import Character
|
||||
from service.fit import Fit
|
||||
|
||||
@@ -50,6 +50,10 @@ class CharacterSelection(wx.Panel):
|
||||
# cache current selection to fall back in case we choose to open char editor
|
||||
self.charCache = None
|
||||
|
||||
# history for Shift-Tab navigation
|
||||
self.charHistory = []
|
||||
self._updatingFromHistory = False
|
||||
|
||||
self.charChoice = wx.Choice(self)
|
||||
mainSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 3)
|
||||
|
||||
@@ -92,7 +96,7 @@ class CharacterSelection(wx.Panel):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
if not fit or not self.needsSkills:
|
||||
if not fit:
|
||||
return
|
||||
|
||||
pos = wx.GetMousePosition()
|
||||
@@ -100,17 +104,23 @@ class CharacterSelection(wx.Panel):
|
||||
|
||||
menu = wx.Menu()
|
||||
|
||||
grantItem = menu.Append(wx.ID_ANY, _t("Grant Missing Skills"))
|
||||
self.Bind(wx.EVT_MENU, self.grantMissingSkills, grantItem)
|
||||
if self.needsSkills:
|
||||
grantItem = menu.Append(wx.ID_ANY, _t("Grant Missing Skills"))
|
||||
self.Bind(wx.EVT_MENU, self.grantMissingSkills, grantItem)
|
||||
|
||||
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills"))
|
||||
self.Bind(wx.EVT_MENU, self.exportSkills, exportItem)
|
||||
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills"))
|
||||
self.Bind(wx.EVT_MENU, self.exportSkills, exportItem)
|
||||
|
||||
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills (condensed)"))
|
||||
self.Bind(wx.EVT_MENU, self.exportSkillsCondensed, exportItem)
|
||||
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills (condensed)"))
|
||||
self.Bind(wx.EVT_MENU, self.exportSkillsCondensed, exportItem)
|
||||
|
||||
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills (EVEMon)"))
|
||||
self.Bind(wx.EVT_MENU, self.exportSkillsEveMon, exportItem)
|
||||
exportItem = menu.Append(wx.ID_ANY, _t("Copy Missing Skills (EVEMon)"))
|
||||
self.Bind(wx.EVT_MENU, self.exportSkillsEveMon, exportItem)
|
||||
|
||||
menu.AppendSeparator()
|
||||
|
||||
importItem = menu.Append(wx.ID_ANY, _t("Import Skills from Clipboard"))
|
||||
self.Bind(wx.EVT_MENU, self.importSkillsFromClipboard, importItem)
|
||||
|
||||
self.PopupMenu(menu, pos)
|
||||
|
||||
@@ -189,6 +199,13 @@ class CharacterSelection(wx.Panel):
|
||||
sFit = Fit.getInstance()
|
||||
sFit.changeChar(fitID, charID)
|
||||
self.charCache = self.charChoice.GetCurrentSelection()
|
||||
|
||||
if not self._updatingFromHistory and charID is not None:
|
||||
currentChar = self.getActiveCharacter()
|
||||
if currentChar is not None:
|
||||
if not self.charHistory or self.charHistory[-1] != currentChar:
|
||||
self.charHistory.append(currentChar)
|
||||
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
|
||||
|
||||
def toggleRefreshButton(self):
|
||||
@@ -211,6 +228,29 @@ class CharacterSelection(wx.Panel):
|
||||
|
||||
return False
|
||||
|
||||
def selectPreviousChar(self):
|
||||
currentChar = self.getActiveCharacter()
|
||||
if currentChar is None:
|
||||
return
|
||||
|
||||
if not self.charHistory:
|
||||
return
|
||||
|
||||
if self.charHistory and self.charHistory[-1] == currentChar:
|
||||
self.charHistory.pop()
|
||||
|
||||
if not self.charHistory:
|
||||
return
|
||||
|
||||
prevChar = self.charHistory.pop()
|
||||
if currentChar != prevChar:
|
||||
self.charHistory.append(currentChar)
|
||||
|
||||
self._updatingFromHistory = True
|
||||
if self.selectChar(prevChar):
|
||||
self.charChanged(None)
|
||||
self._updatingFromHistory = False
|
||||
|
||||
def fitChanged(self, event):
|
||||
"""
|
||||
When fit is changed, or new fit is selected
|
||||
@@ -222,7 +262,7 @@ class CharacterSelection(wx.Panel):
|
||||
self.charChoice.Enable(activeFitID is not None)
|
||||
choice = self.charChoice
|
||||
sFit = Fit.getInstance()
|
||||
currCharID = choice.GetClientData(choice.GetCurrentSelection())
|
||||
currCharID = choice.GetClientData(choice.GetCurrentSelection()) if choice.GetCurrentSelection() != -1 else None
|
||||
fit = sFit.getFit(activeFitID)
|
||||
newCharID = fit.character.ID if fit is not None else None
|
||||
|
||||
@@ -256,6 +296,9 @@ class CharacterSelection(wx.Panel):
|
||||
self.selectChar(sChar.all5ID())
|
||||
|
||||
elif currCharID != newCharID:
|
||||
if currCharID is not None and not self._updatingFromHistory:
|
||||
if not self.charHistory or self.charHistory[-1] != currCharID:
|
||||
self.charHistory.append(currCharID)
|
||||
self.selectChar(newCharID)
|
||||
if not fit.calculated:
|
||||
self.charChanged(None)
|
||||
@@ -289,6 +332,83 @@ class CharacterSelection(wx.Panel):
|
||||
|
||||
toClipboard(list)
|
||||
|
||||
def importSkillsFromClipboard(self, evt):
|
||||
charID = self.getActiveCharacter()
|
||||
if charID is None:
|
||||
return
|
||||
|
||||
sChar = Character.getInstance()
|
||||
char = sChar.getCharacter(charID)
|
||||
|
||||
text = fromClipboard()
|
||||
if not text:
|
||||
with wx.MessageDialog(self, _t("Clipboard is empty"), _t("Error"), wx.OK | wx.ICON_ERROR) as dlg:
|
||||
dlg.ShowModal()
|
||||
return
|
||||
|
||||
try:
|
||||
lines = text.strip().splitlines()
|
||||
imported = 0
|
||||
errors = []
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
try:
|
||||
parts = line.rsplit(None, 1)
|
||||
if len(parts) != 2:
|
||||
errors.append(_t("Invalid format: {}").format(line))
|
||||
continue
|
||||
|
||||
skillName = parts[0]
|
||||
levelStr = parts[1]
|
||||
|
||||
try:
|
||||
level = int(levelStr)
|
||||
except ValueError:
|
||||
try:
|
||||
level = roman.fromRoman(levelStr.upper())
|
||||
except (roman.InvalidRomanNumeralError, ValueError):
|
||||
errors.append(_t("Invalid level format: {}").format(line))
|
||||
continue
|
||||
|
||||
if level < 0 or level > 5:
|
||||
errors.append(_t("Level must be between 0 and 5: {}").format(line))
|
||||
continue
|
||||
|
||||
skill = char.getSkill(skillName)
|
||||
sChar.changeLevel(charID, skill.item.ID, level)
|
||||
imported += 1
|
||||
|
||||
except KeyError as e:
|
||||
errors.append(_t("Skill not found: {}").format(skillName))
|
||||
pyfalog.error("Skill not found: '{}'", skillName)
|
||||
except Exception as e:
|
||||
errors.append(_t("Error processing line '{}': {}").format(line, str(e)))
|
||||
pyfalog.error("Error importing skill from line '{}': {}", line, e)
|
||||
|
||||
if imported > 0:
|
||||
self.refreshCharacterList()
|
||||
wx.PostEvent(self.mainFrame, GE.CharListUpdated())
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is not None:
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
|
||||
|
||||
if errors:
|
||||
errorMsg = _t("Imported {} skill(s). Errors:\n{}").format(imported, "\n".join(errors))
|
||||
with wx.MessageDialog(self, errorMsg, _t("Import Skills"), wx.OK | wx.ICON_WARNING) as dlg:
|
||||
dlg.ShowModal()
|
||||
elif imported > 0:
|
||||
with wx.MessageDialog(self, _t("Successfully imported {} skill(s)").format(imported), _t("Import Skills"), wx.OK) as dlg:
|
||||
dlg.ShowModal()
|
||||
|
||||
except Exception as e:
|
||||
pyfalog.error("Error importing skills from clipboard: {}", e)
|
||||
with wx.MessageDialog(self, _t("Error importing skills. Please check the log file."), _t("Error"), wx.OK | wx.ICON_ERROR) as dlg:
|
||||
dlg.ShowModal()
|
||||
|
||||
def _buildSkillsTooltip(self, reqs, currItem="", tabulationLevel=0):
|
||||
tip = ""
|
||||
sCharacter = Character.getInstance()
|
||||
|
||||
@@ -59,6 +59,12 @@ from .gui.localModule.mutatedRevert import GuiRevertMutatedLocalModuleCommand
|
||||
from .gui.localModule.remove import GuiRemoveLocalModuleCommand
|
||||
from .gui.localModule.replace import GuiReplaceLocalModuleCommand
|
||||
from .gui.localModule.swap import GuiSwapLocalModulesCommand
|
||||
from .gui.localModuleCargo.batchCargoToLocalModule import (
|
||||
GuiBatchCargoToLocalModuleCommand,
|
||||
)
|
||||
from .gui.localModuleCargo.batchLocalModuleToCargo import (
|
||||
GuiBatchLocalModuleToCargoCommand,
|
||||
)
|
||||
from .gui.localModuleCargo.cargoToLocalModule import GuiCargoToLocalModuleCommand
|
||||
from .gui.localModuleCargo.localModuleToCargo import GuiLocalModuleToCargoCommand
|
||||
from .gui.projectedChangeProjectionRange import GuiChangeProjectedItemsProjectionRangeCommand
|
||||
|
||||
325
gui/fitCommands/gui/localModuleCargo/batchCargoToLocalModule.py
Normal file
325
gui/fitCommands/gui/localModuleCargo/batchCargoToLocalModule.py
Normal file
@@ -0,0 +1,325 @@
|
||||
import wx
|
||||
|
||||
import eos.db
|
||||
import gui.mainFrame
|
||||
from gui import globalEvents as GE
|
||||
from gui.fitCommands.calc.cargo.add import CalcAddCargoCommand
|
||||
from gui.fitCommands.calc.cargo.remove import CalcRemoveCargoCommand
|
||||
from gui.fitCommands.calc.module.changeCharges import CalcChangeModuleChargesCommand
|
||||
from gui.fitCommands.calc.module.localReplace import CalcReplaceLocalModuleCommand
|
||||
from gui.fitCommands.helpers import (
|
||||
CargoInfo,
|
||||
InternalCommandHistory,
|
||||
ModuleInfo,
|
||||
restoreRemovedDummies,
|
||||
)
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class GuiBatchCargoToLocalModuleCommand(wx.Command):
|
||||
def __init__(self, fitID, cargoItemID, targetPosition, copy):
|
||||
wx.Command.__init__(self, True, "Batch Cargo to Local Modules")
|
||||
self.internalHistory = InternalCommandHistory()
|
||||
self.fitID = fitID
|
||||
self.srcCargoItemID = cargoItemID
|
||||
self.targetPosition = targetPosition
|
||||
self.copy = copy
|
||||
self.replacedModItemIDs = []
|
||||
self.savedRemovedDummies = None
|
||||
|
||||
def Do(self):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.fitID)
|
||||
if fit is None:
|
||||
return False
|
||||
|
||||
srcCargo = next((c for c in fit.cargo if c.itemID == self.srcCargoItemID), None)
|
||||
if srcCargo is None:
|
||||
return False
|
||||
|
||||
if srcCargo.item.isCharge:
|
||||
return self._handleCharges(fit, srcCargo)
|
||||
|
||||
if not srcCargo.item.isModule:
|
||||
return False
|
||||
|
||||
if self.targetPosition >= len(fit.modules):
|
||||
return False
|
||||
|
||||
targetMod = fit.modules[self.targetPosition]
|
||||
|
||||
if targetMod.isEmpty:
|
||||
return self._fillEmptySlots(fit, srcCargo, targetMod.slot)
|
||||
else:
|
||||
return self._replaceSimilarModules(fit, srcCargo, targetMod)
|
||||
|
||||
def _getSimilarModulePositions(self, fit, targetMod):
|
||||
targetItemID = targetMod.itemID
|
||||
matchingPositions = []
|
||||
for position, mod in enumerate(fit.modules):
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
if mod.itemID == targetItemID:
|
||||
matchingPositions.append(position)
|
||||
return matchingPositions
|
||||
|
||||
def _replaceSimilarModules(self, fit, srcCargo, targetMod):
|
||||
availableAmount = srcCargo.amount if not self.copy else float("inf")
|
||||
|
||||
matchingPositions = self._getSimilarModulePositions(fit, targetMod)
|
||||
|
||||
if not matchingPositions:
|
||||
return False
|
||||
|
||||
positionsToReplace = matchingPositions[: int(availableAmount)]
|
||||
|
||||
if not positionsToReplace:
|
||||
return False
|
||||
|
||||
self.replacedModItemIDs = []
|
||||
commands = []
|
||||
cargoToRemove = 0
|
||||
|
||||
for position in positionsToReplace:
|
||||
mod = fit.modules[position]
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
dstModItemID = mod.itemID
|
||||
|
||||
newModInfo = ModuleInfo.fromModule(mod, unmutate=True)
|
||||
newModInfo.itemID = self.srcCargoItemID
|
||||
newCargoModItemID = ModuleInfo.fromModule(mod, unmutate=True).itemID
|
||||
|
||||
commands.append(
|
||||
CalcAddCargoCommand(
|
||||
fitID=self.fitID,
|
||||
cargoInfo=CargoInfo(itemID=newCargoModItemID, amount=1),
|
||||
)
|
||||
)
|
||||
cmdReplace = CalcReplaceLocalModuleCommand(
|
||||
fitID=self.fitID,
|
||||
position=position,
|
||||
newModInfo=newModInfo,
|
||||
unloadInvalidCharges=True,
|
||||
)
|
||||
commands.append(cmdReplace)
|
||||
self.replacedModItemIDs.append(dstModItemID)
|
||||
cargoToRemove += 1
|
||||
|
||||
if not self.copy and cargoToRemove > 0:
|
||||
commands.insert(
|
||||
0,
|
||||
CalcRemoveCargoCommand(
|
||||
fitID=self.fitID,
|
||||
cargoInfo=CargoInfo(
|
||||
itemID=self.srcCargoItemID, amount=cargoToRemove
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
success = self.internalHistory.submitBatch(*commands)
|
||||
|
||||
if not success:
|
||||
self.internalHistory.undoAll()
|
||||
return False
|
||||
|
||||
eos.db.flush()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.recalc(self.fitID)
|
||||
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
|
||||
events = []
|
||||
for removedModItemID in self.replacedModItemIDs:
|
||||
events.append(
|
||||
GE.FitChanged(
|
||||
fitIDs=(self.fitID,), action="moddel", typeID=removedModItemID
|
||||
)
|
||||
)
|
||||
if self.srcCargoItemID is not None:
|
||||
events.append(
|
||||
GE.FitChanged(
|
||||
fitIDs=(self.fitID,), action="modadd", typeID=self.srcCargoItemID
|
||||
)
|
||||
)
|
||||
if not events:
|
||||
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||
for event in events:
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||
|
||||
return True
|
||||
|
||||
def _fillEmptySlots(self, fit, srcCargo, targetSlot):
|
||||
availableAmount = srcCargo.amount if not self.copy else float("inf")
|
||||
|
||||
emptyPositions = []
|
||||
for position, mod in enumerate(fit.modules):
|
||||
if mod.isEmpty and mod.slot == targetSlot:
|
||||
emptyPositions.append(position)
|
||||
|
||||
if not emptyPositions:
|
||||
return False
|
||||
|
||||
positionsToFill = emptyPositions[: int(availableAmount)]
|
||||
|
||||
if not positionsToFill:
|
||||
return False
|
||||
|
||||
commands = []
|
||||
cargoToRemove = 0
|
||||
|
||||
for position in positionsToFill:
|
||||
newModInfo = ModuleInfo(itemID=self.srcCargoItemID)
|
||||
cmdReplace = CalcReplaceLocalModuleCommand(
|
||||
fitID=self.fitID,
|
||||
position=position,
|
||||
newModInfo=newModInfo,
|
||||
unloadInvalidCharges=True,
|
||||
)
|
||||
commands.append(cmdReplace)
|
||||
cargoToRemove += 1
|
||||
|
||||
if not self.copy and cargoToRemove > 0:
|
||||
commands.insert(
|
||||
0,
|
||||
CalcRemoveCargoCommand(
|
||||
fitID=self.fitID,
|
||||
cargoInfo=CargoInfo(
|
||||
itemID=self.srcCargoItemID, amount=cargoToRemove
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
success = self.internalHistory.submitBatch(*commands)
|
||||
|
||||
if not success:
|
||||
self.internalHistory.undoAll()
|
||||
return False
|
||||
|
||||
eos.db.flush()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.recalc(self.fitID)
|
||||
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
|
||||
events = []
|
||||
if self.srcCargoItemID is not None:
|
||||
events.append(
|
||||
GE.FitChanged(
|
||||
fitIDs=(self.fitID,), action="modadd", typeID=self.srcCargoItemID
|
||||
)
|
||||
)
|
||||
if not events:
|
||||
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||
for event in events:
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||
|
||||
return True
|
||||
|
||||
def _handleCharges(self, fit, srcCargo):
|
||||
availableAmount = srcCargo.amount if not self.copy else float("inf")
|
||||
|
||||
targetMod = fit.modules[self.targetPosition]
|
||||
if targetMod.isEmpty:
|
||||
return False
|
||||
|
||||
targetItemID = targetMod.itemID
|
||||
matchingPositions = []
|
||||
for position, mod in enumerate(fit.modules):
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
if mod.itemID == targetItemID:
|
||||
matchingPositions.append(position)
|
||||
|
||||
if not matchingPositions:
|
||||
return False
|
||||
|
||||
positionsToReplace = matchingPositions[: int(availableAmount)]
|
||||
if not positionsToReplace:
|
||||
return False
|
||||
|
||||
commands = []
|
||||
chargeMap = {}
|
||||
totalChargesNeeded = 0
|
||||
|
||||
for position in positionsToReplace:
|
||||
mod = fit.modules[position]
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
|
||||
oldChargeID = mod.chargeID
|
||||
oldChargeAmount = mod.numCharges
|
||||
newChargeAmount = mod.getNumCharges(srcCargo.item)
|
||||
|
||||
if oldChargeID is not None and oldChargeID != srcCargo.itemID:
|
||||
commands.append(
|
||||
CalcAddCargoCommand(
|
||||
fitID=self.fitID,
|
||||
cargoInfo=CargoInfo(itemID=oldChargeID, amount=oldChargeAmount),
|
||||
)
|
||||
)
|
||||
|
||||
chargeMap[position] = srcCargo.itemID
|
||||
totalChargesNeeded += newChargeAmount
|
||||
|
||||
if not self.copy and totalChargesNeeded > 0:
|
||||
commands.append(
|
||||
CalcRemoveCargoCommand(
|
||||
fitID=self.fitID,
|
||||
cargoInfo=CargoInfo(
|
||||
itemID=srcCargo.itemID, amount=totalChargesNeeded
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
commands.append(
|
||||
CalcChangeModuleChargesCommand(
|
||||
fitID=self.fitID, projected=False, chargeMap=chargeMap
|
||||
)
|
||||
)
|
||||
|
||||
success = self.internalHistory.submitBatch(*commands)
|
||||
|
||||
if not success:
|
||||
self.internalHistory.undoAll()
|
||||
return False
|
||||
|
||||
eos.db.flush()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.recalc(self.fitID)
|
||||
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
|
||||
events = [GE.FitChanged(fitIDs=(self.fitID,))]
|
||||
for event in events:
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.fitID)
|
||||
restoreRemovedDummies(fit, self.savedRemovedDummies)
|
||||
success = self.internalHistory.undoAll()
|
||||
eos.db.flush()
|
||||
sFit.recalc(self.fitID)
|
||||
sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
events = []
|
||||
if self.srcCargoItemID is not None:
|
||||
events.append(
|
||||
GE.FitChanged(
|
||||
fitIDs=(self.fitID,), action="moddel", typeID=self.srcCargoItemID
|
||||
)
|
||||
)
|
||||
for removedModItemID in self.replacedModItemIDs:
|
||||
events.append(
|
||||
GE.FitChanged(
|
||||
fitIDs=(self.fitID,), action="modadd", typeID=removedModItemID
|
||||
)
|
||||
)
|
||||
if not events:
|
||||
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||
for event in events:
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||
return success
|
||||
167
gui/fitCommands/gui/localModuleCargo/batchLocalModuleToCargo.py
Normal file
167
gui/fitCommands/gui/localModuleCargo/batchLocalModuleToCargo.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import wx
|
||||
|
||||
import eos.db
|
||||
import gui.mainFrame
|
||||
from gui import globalEvents as GE
|
||||
from gui.fitCommands.calc.cargo.add import CalcAddCargoCommand
|
||||
from gui.fitCommands.calc.module.localRemove import CalcRemoveLocalModulesCommand
|
||||
from gui.fitCommands.helpers import (
|
||||
CargoInfo,
|
||||
InternalCommandHistory,
|
||||
ModuleInfo,
|
||||
restoreRemovedDummies,
|
||||
)
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class GuiBatchLocalModuleToCargoCommand(wx.Command):
|
||||
def __init__(self, fitID, modPosition, copy):
|
||||
wx.Command.__init__(self, True, "Batch Local Module to Cargo")
|
||||
self.internalHistory = InternalCommandHistory()
|
||||
self.fitID = fitID
|
||||
self.srcModPosition = modPosition
|
||||
self.copy = copy
|
||||
self.removedModItemIDs = []
|
||||
self.savedRemovedDummies = None
|
||||
|
||||
def Do(self):
|
||||
fit = Fit.getInstance().getFit(self.fitID)
|
||||
srcMod = fit.modules[self.srcModPosition]
|
||||
if srcMod.isEmpty:
|
||||
return False
|
||||
|
||||
if srcMod.chargeID is not None:
|
||||
return self._unloadCharges(fit, srcMod)
|
||||
else:
|
||||
return self._moveModulesToCargo(fit, srcMod)
|
||||
|
||||
def _getSimilarModulePositions(self, fit, targetMod):
|
||||
targetItemID = targetMod.itemID
|
||||
matchingPositions = []
|
||||
for position, mod in enumerate(fit.modules):
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
if mod.itemID == targetItemID:
|
||||
matchingPositions.append(position)
|
||||
return matchingPositions
|
||||
|
||||
def _unloadCharges(self, fit, srcMod):
|
||||
matchingPositions = self._getSimilarModulePositions(fit, srcMod)
|
||||
if not matchingPositions:
|
||||
return False
|
||||
|
||||
commands = []
|
||||
for position in matchingPositions:
|
||||
mod = fit.modules[position]
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
if mod.chargeID is not None:
|
||||
commands.append(
|
||||
CalcAddCargoCommand(
|
||||
fitID=self.fitID,
|
||||
cargoInfo=CargoInfo(itemID=mod.chargeID, amount=mod.numCharges),
|
||||
)
|
||||
)
|
||||
|
||||
if not self.copy:
|
||||
from gui.fitCommands.calc.module.changeCharges import (
|
||||
CalcChangeModuleChargesCommand,
|
||||
)
|
||||
|
||||
chargeMap = {pos: None for pos in matchingPositions}
|
||||
commands.append(
|
||||
CalcChangeModuleChargesCommand(
|
||||
fitID=self.fitID, projected=False, chargeMap=chargeMap
|
||||
)
|
||||
)
|
||||
|
||||
success = self.internalHistory.submitBatch(*commands)
|
||||
if not success:
|
||||
self.internalHistory.undoAll()
|
||||
return False
|
||||
|
||||
eos.db.flush()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.recalc(self.fitID)
|
||||
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
|
||||
events = [GE.FitChanged(fitIDs=(self.fitID,))]
|
||||
for event in events:
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||
return success
|
||||
|
||||
def _moveModulesToCargo(self, fit, srcMod):
|
||||
matchingPositions = self._getSimilarModulePositions(fit, srcMod)
|
||||
if not matchingPositions:
|
||||
return False
|
||||
|
||||
commands = []
|
||||
for position in matchingPositions:
|
||||
mod = fit.modules[position]
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
|
||||
commands.append(
|
||||
CalcAddCargoCommand(
|
||||
fitID=self.fitID,
|
||||
cargoInfo=CargoInfo(
|
||||
itemID=ModuleInfo.fromModule(mod, unmutate=True).itemID,
|
||||
amount=1,
|
||||
),
|
||||
)
|
||||
)
|
||||
self.removedModItemIDs.append(mod.itemID)
|
||||
|
||||
if not self.copy:
|
||||
commands.append(
|
||||
CalcRemoveLocalModulesCommand(
|
||||
fitID=self.fitID, positions=matchingPositions
|
||||
)
|
||||
)
|
||||
|
||||
success = self.internalHistory.submitBatch(*commands)
|
||||
if not success:
|
||||
self.internalHistory.undoAll()
|
||||
return False
|
||||
|
||||
eos.db.flush()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.recalc(self.fitID)
|
||||
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
|
||||
events = []
|
||||
for removedModItemID in self.removedModItemIDs:
|
||||
events.append(
|
||||
GE.FitChanged(
|
||||
fitIDs=(self.fitID,), action="moddel", typeID=removedModItemID
|
||||
)
|
||||
)
|
||||
if not events:
|
||||
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||
for event in events:
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||
return success
|
||||
|
||||
def Undo(self):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.fitID)
|
||||
restoreRemovedDummies(fit, self.savedRemovedDummies)
|
||||
success = self.internalHistory.undoAll()
|
||||
eos.db.flush()
|
||||
sFit.recalc(self.fitID)
|
||||
sFit.fill(self.fitID)
|
||||
eos.db.commit()
|
||||
events = []
|
||||
for removedModItemID in self.removedModItemIDs:
|
||||
events.append(
|
||||
GE.FitChanged(
|
||||
fitIDs=(self.fitID,), action="modadd", typeID=removedModItemID
|
||||
)
|
||||
)
|
||||
if not events:
|
||||
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||
for event in events:
|
||||
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||
return success
|
||||
216
gui/fitDiffFrame.py
Normal file
216
gui/fitDiffFrame.py
Normal file
@@ -0,0 +1,216 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2025
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import re
|
||||
|
||||
from service.fit import Fit as svcFit
|
||||
from service.port.eft import exportEft
|
||||
from service.const import PortEftOptions
|
||||
|
||||
_t = wx.GetTranslation
|
||||
|
||||
# Regex for parsing items: itemName x? quantity?, ,? chargeName?
|
||||
ITEM_REGEX = re.compile(
|
||||
r"^(?P<itemName>[-\'\w\s]+?)x?\s*(?P<quantity>\d+)?\s*(?:,\s*(?P<chargeName>[-\'\w\s]+))?$"
|
||||
)
|
||||
|
||||
|
||||
class FitDiffFrame(wx.Frame):
|
||||
"""A frame to display differences between two fits."""
|
||||
|
||||
def __init__(self, parent, fitID):
|
||||
super().__init__(
|
||||
parent,
|
||||
title=_t("Fit Diff"),
|
||||
style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
|
||||
size=(1000, 600)
|
||||
)
|
||||
self.parent = parent
|
||||
self.fitID = fitID
|
||||
self.sFit = svcFit.getInstance()
|
||||
|
||||
# EFT export options (same as CTRL-C)
|
||||
self.eftOptions = {
|
||||
PortEftOptions.LOADED_CHARGES: True,
|
||||
PortEftOptions.MUTATIONS: True,
|
||||
PortEftOptions.IMPLANTS: True,
|
||||
PortEftOptions.BOOSTERS: True,
|
||||
PortEftOptions.CARGO: True,
|
||||
}
|
||||
|
||||
self.initUI()
|
||||
self.Centre()
|
||||
self.Show()
|
||||
|
||||
def initUI(self):
|
||||
panel = wx.Panel(self)
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
# Instructions and flip button at the top
|
||||
topSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
instructions = wx.StaticText(
|
||||
panel,
|
||||
label=_t("Paste fits in EFT format to compare")
|
||||
)
|
||||
topSizer.Add(instructions, 1, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
flipButton = wx.Button(panel, label=_t("Flip"))
|
||||
flipButton.Bind(wx.EVT_BUTTON, self.onFlip)
|
||||
topSizer.Add(flipButton, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
mainSizer.Add(topSizer, 0, wx.EXPAND)
|
||||
|
||||
# Three panes: Fit 1 | Diff | Fit 2
|
||||
panesSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
# Pane 1: Fit 1 (editable)
|
||||
fit1Box = wx.StaticBox(panel, label=_t("Fit 1"))
|
||||
fit1Sizer = wx.StaticBoxSizer(fit1Box, wx.VERTICAL)
|
||||
self.fit1Text = wx.TextCtrl(
|
||||
panel,
|
||||
style=wx.TE_MULTILINE | wx.TE_DONTWRAP
|
||||
)
|
||||
fit1Sizer.Add(self.fit1Text, 1, wx.EXPAND)
|
||||
panesSizer.Add(fit1Sizer, 1, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
# Bind text changed event to update diff
|
||||
self.fit1Text.Bind(wx.EVT_TEXT, self.onFitChanged)
|
||||
|
||||
# Pane 2: Diff (simple text format)
|
||||
diffBox = wx.StaticBox(panel, label=_t("Differences"))
|
||||
diffSizer = wx.StaticBoxSizer(diffBox, wx.VERTICAL)
|
||||
self.diffText = wx.TextCtrl(
|
||||
panel,
|
||||
style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP
|
||||
)
|
||||
diffSizer.Add(self.diffText, 1, wx.EXPAND)
|
||||
panesSizer.Add(diffSizer, 1, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
# Pane 3: Fit 2 (user input)
|
||||
fit2Box = wx.StaticBox(panel, label=_t("Fit 2"))
|
||||
fit2Sizer = wx.StaticBoxSizer(fit2Box, wx.VERTICAL)
|
||||
self.fit2Text = wx.TextCtrl(
|
||||
panel,
|
||||
style=wx.TE_MULTILINE | wx.TE_DONTWRAP
|
||||
)
|
||||
fit2Sizer.Add(self.fit2Text, 1, wx.EXPAND)
|
||||
|
||||
# Bind text changed event to update diff
|
||||
self.fit2Text.Bind(wx.EVT_TEXT, self.onFitChanged)
|
||||
|
||||
panesSizer.Add(fit2Sizer, 1, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
mainSizer.Add(panesSizer, 1, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
panel.SetSizer(mainSizer)
|
||||
|
||||
# Load current fit into pane 1
|
||||
self.loadFit1()
|
||||
|
||||
def loadFit1(self):
|
||||
"""Load the current fit into pane 1 as EFT format."""
|
||||
fit = self.sFit.getFit(self.fitID)
|
||||
if fit:
|
||||
eftText = exportEft(fit, self.eftOptions, callback=None)
|
||||
self.fit1Text.SetValue(eftText)
|
||||
|
||||
def onFitChanged(self, event):
|
||||
"""Handle text change in either fit pane - update diff."""
|
||||
self.updateDiff()
|
||||
event.Skip()
|
||||
|
||||
def onFlip(self, event):
|
||||
"""Swap Fit 1 and Fit 2."""
|
||||
fit1Value = self.fit1Text.GetValue()
|
||||
fit2Value = self.fit2Text.GetValue()
|
||||
self.fit1Text.SetValue(fit2Value)
|
||||
self.fit2Text.SetValue(fit1Value)
|
||||
self.updateDiff()
|
||||
event.Skip()
|
||||
|
||||
def updateDiff(self):
|
||||
"""Calculate and display the differences between the two fits."""
|
||||
self.diffText.Clear()
|
||||
|
||||
fit1Text = self.fit1Text.GetValue().strip()
|
||||
fit2Text = self.fit2Text.GetValue().strip()
|
||||
|
||||
if not fit1Text or not fit2Text:
|
||||
return
|
||||
|
||||
# Parse both fits
|
||||
fit1 = self.parsePastedFit(fit1Text)
|
||||
fit2 = self.parsePastedFit(fit2Text)
|
||||
|
||||
if fit1 is None:
|
||||
self.diffText.SetValue(_t("Error: Fit 1 has invalid EFT format"))
|
||||
return
|
||||
if fit2 is None:
|
||||
self.diffText.SetValue(_t("Error: Fit 2 has invalid EFT format"))
|
||||
return
|
||||
|
||||
# Calculate differences and format as simple text list
|
||||
diffLines = self.calculateDiff(fit1, fit2)
|
||||
self.diffText.SetValue('\n'.join(diffLines))
|
||||
|
||||
def parsePastedFit(self, text):
|
||||
"""Parse pasted EFT text into a map of item name to count."""
|
||||
items = {}
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("["):
|
||||
continue
|
||||
match = ITEM_REGEX.match(line)
|
||||
if match:
|
||||
item_name = match.group("itemName").strip()
|
||||
quantity = match.group("quantity")
|
||||
count = int(quantity) if quantity else 1
|
||||
if item_name not in items:
|
||||
items[item_name] = 0
|
||||
items[item_name] += count
|
||||
return items
|
||||
|
||||
def calculateDiff(self, fit1_items, fit2_items):
|
||||
"""Calculate items needed to transform fit1 into fit2.
|
||||
|
||||
Returns a list of strings showing additions and extra items.
|
||||
"""
|
||||
diffLines = []
|
||||
|
||||
all_items = set(fit1_items.keys()) | set(fit2_items.keys())
|
||||
additions = []
|
||||
extras = []
|
||||
|
||||
for item in sorted(all_items):
|
||||
count1 = fit1_items.get(item, 0)
|
||||
count2 = fit2_items.get(item, 0)
|
||||
if count2 > count1:
|
||||
additions.append(f"{item} x{count2 - count1}")
|
||||
elif count1 > count2:
|
||||
extras.append(f"{item} x-{count1 - count2}")
|
||||
|
||||
diffLines.extend(additions)
|
||||
if additions and extras:
|
||||
diffLines.extend(["", ""])
|
||||
diffLines.extend(extras)
|
||||
|
||||
return diffLines
|
||||
@@ -24,6 +24,7 @@ import config
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
|
||||
@@ -35,6 +36,7 @@ from gui.builtinItemStatsViews.itemEffects import ItemEffects
|
||||
from gui.builtinItemStatsViews.itemMutator import ItemMutatorPanel
|
||||
from gui.builtinItemStatsViews.itemProperties import ItemProperties
|
||||
from gui.builtinItemStatsViews.itemRequirements import ItemRequirements
|
||||
from gui.builtinItemStatsViews.itemSkills import ItemSkills
|
||||
from gui.builtinItemStatsViews.itemTraits import ItemTraits
|
||||
from service.market import Market
|
||||
|
||||
@@ -156,6 +158,8 @@ class ItemStatsContainer(wx.Panel):
|
||||
def __init__(self, parent, stuff, item, context=None):
|
||||
wx.Panel.__init__(self, parent)
|
||||
sMkt = Market.getInstance()
|
||||
self.stuff = stuff
|
||||
self.context = context
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
@@ -196,6 +200,10 @@ class ItemStatsContainer(wx.Panel):
|
||||
self.affectedby = ItemAffectedBy(self.nbContainer, stuff, item)
|
||||
self.nbContainer.AddPage(self.affectedby, _t("Affected by"))
|
||||
|
||||
if stuff is not None and isinstance(stuff, Ship):
|
||||
self.skills = ItemSkills(self.nbContainer, stuff, item)
|
||||
self.nbContainer.AddPage(self.skills, _t("Skills"))
|
||||
|
||||
if config.debug:
|
||||
self.properties = ItemProperties(self.nbContainer, stuff, item, context)
|
||||
self.nbContainer.AddPage(self.properties, _t("Properties"))
|
||||
|
||||
112
gui/mainFrame.py
112
gui/mainFrame.py
@@ -18,6 +18,7 @@
|
||||
# =============================================================================
|
||||
|
||||
import datetime
|
||||
import itertools
|
||||
import os.path
|
||||
import threading
|
||||
import time
|
||||
@@ -60,8 +61,12 @@ from gui.shipBrowser import ShipBrowser
|
||||
from gui.statsPane import StatsPane
|
||||
from gui.targetProfileEditor import TargetProfileEditor
|
||||
from gui.updateDialog import UpdateDialog
|
||||
from gui.utils.clipboard import fromClipboard
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
from gui.utils.progressHelper import ProgressHelper
|
||||
from eos.const import FittingSlot as es_Slot
|
||||
from eos.saveddata.character import Skill
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
from eos.saveddata.module import Module as es_Module
|
||||
from service.character import Character
|
||||
from service.esi import Esi
|
||||
from service.fit import Fit
|
||||
@@ -522,6 +527,8 @@ class MainFrame(wx.Frame):
|
||||
self.Bind(wx.EVT_MENU, self.backupToXml, id=menuBar.backupFitsId)
|
||||
# Export skills needed
|
||||
self.Bind(wx.EVT_MENU, self.exportSkillsNeeded, id=menuBar.exportSkillsNeededId)
|
||||
# Copy skills needed
|
||||
self.Bind(wx.EVT_MENU, self.copySkillsNeeded, id=menuBar.copySkillsNeededId)
|
||||
# Import character
|
||||
self.Bind(wx.EVT_MENU, self.importCharacter, id=menuBar.importCharacterId)
|
||||
# Export HTML
|
||||
@@ -558,7 +565,8 @@ class MainFrame(wx.Frame):
|
||||
self.Bind(wx.EVT_MENU, self.toggleOverrides, id=menuBar.toggleOverridesId)
|
||||
|
||||
# Clipboard exports
|
||||
self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY)
|
||||
self.Bind(wx.EVT_MENU, self.exportToClipboardDirectEft, id=menuBar.copyDirectEftId)
|
||||
self.Bind(wx.EVT_MENU, self.exportToClipboard, id=menuBar.copyWithDialogId)
|
||||
|
||||
# Fitting Restrictions
|
||||
self.Bind(wx.EVT_MENU, self.toggleIgnoreRestriction, id=menuBar.toggleIgnoreRestrictionID)
|
||||
@@ -571,6 +579,7 @@ class MainFrame(wx.Frame):
|
||||
toggleShipMarketId = wx.NewId()
|
||||
ctabnext = wx.NewId()
|
||||
ctabprev = wx.NewId()
|
||||
charPrevId = wx.NewId()
|
||||
|
||||
# Close Page
|
||||
self.Bind(wx.EVT_MENU, self.CloseCurrentPage, id=self.closePageId)
|
||||
@@ -580,6 +589,7 @@ class MainFrame(wx.Frame):
|
||||
self.Bind(wx.EVT_MENU, self.toggleShipMarket, id=toggleShipMarketId)
|
||||
self.Bind(wx.EVT_MENU, self.CTabNext, id=ctabnext)
|
||||
self.Bind(wx.EVT_MENU, self.CTabPrev, id=ctabprev)
|
||||
self.Bind(wx.EVT_MENU, self.selectPreviousCharacter, id=charPrevId)
|
||||
|
||||
actb = [(wx.ACCEL_CTRL, ord('T'), self.addPageId),
|
||||
(wx.ACCEL_CMD, ord('T'), self.addPageId),
|
||||
@@ -613,7 +623,19 @@ class MainFrame(wx.Frame):
|
||||
(wx.ACCEL_CMD, wx.WXK_PAGEDOWN, ctabnext),
|
||||
(wx.ACCEL_CMD, wx.WXK_PAGEUP, ctabprev),
|
||||
|
||||
(wx.ACCEL_CMD | wx.ACCEL_SHIFT, ord("Z"), wx.ID_REDO)
|
||||
(wx.ACCEL_CMD | wx.ACCEL_SHIFT, ord("Z"), wx.ID_REDO),
|
||||
|
||||
# Ctrl+Shift+C for copy with dialog (must come before Ctrl+C)
|
||||
# Note: use lowercase 'c' because SHIFT is already in flags
|
||||
(wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord('c'), menuBar.copyWithDialogId),
|
||||
(wx.ACCEL_CMD | wx.ACCEL_SHIFT, ord('c'), menuBar.copyWithDialogId),
|
||||
|
||||
# Ctrl+C for direct EFT copy
|
||||
(wx.ACCEL_CTRL, ord('c'), menuBar.copyDirectEftId),
|
||||
(wx.ACCEL_CMD, ord('c'), menuBar.copyDirectEftId),
|
||||
|
||||
# Shift+Tab for previous character
|
||||
(wx.ACCEL_SHIFT, wx.WXK_TAB, charPrevId)
|
||||
]
|
||||
|
||||
# Ctrl/Cmd+# for addition pane selection
|
||||
@@ -740,6 +762,9 @@ class MainFrame(wx.Frame):
|
||||
def CTabPrev(self, event):
|
||||
self.fitMultiSwitch.PrevPage()
|
||||
|
||||
def selectPreviousCharacter(self, event):
|
||||
self.charSelection.selectPreviousChar()
|
||||
|
||||
def HAddPage(self, event):
|
||||
self.fitMultiSwitch.AddPage()
|
||||
|
||||
@@ -798,6 +823,32 @@ class MainFrame(wx.Frame):
|
||||
else:
|
||||
self._openAfterImport(importData)
|
||||
|
||||
def exportToClipboardDirectEft(self, event):
|
||||
""" Copy fit to clipboard in EFT format without showing dialog """
|
||||
from eos.db import getFit
|
||||
from service.const import PortEftOptions
|
||||
from service.settings import SettingsProvider
|
||||
|
||||
fit = getFit(self.getActiveFit())
|
||||
if fit is None:
|
||||
return
|
||||
|
||||
# Get the default EFT export options from settings
|
||||
defaultOptions = {
|
||||
PortEftOptions.LOADED_CHARGES: True,
|
||||
PortEftOptions.MUTATIONS: True,
|
||||
PortEftOptions.IMPLANTS: True,
|
||||
PortEftOptions.BOOSTERS: True,
|
||||
PortEftOptions.CARGO: True,
|
||||
}
|
||||
settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": CopySelectDialog.copyFormatEft, "options": {CopySelectDialog.copyFormatEft: defaultOptions}})
|
||||
options = settings["options"].get(CopySelectDialog.copyFormatEft, defaultOptions)
|
||||
|
||||
def copyToClipboard(text):
|
||||
toClipboard(text)
|
||||
|
||||
Port.exportEft(fit, options, callback=copyToClipboard)
|
||||
|
||||
def exportToClipboard(self, event):
|
||||
with CopySelectDialog(self) as dlg:
|
||||
dlg.ShowModal()
|
||||
@@ -832,6 +883,61 @@ class MainFrame(wx.Frame):
|
||||
self.waitDialog = wx.BusyInfo(_t("Exporting skills needed..."), parent=self)
|
||||
sCharacter.backupSkills(filePath, saveFmt, self.getActiveFit(), self.closeWaitDialog)
|
||||
|
||||
def copySkillsNeeded(self, event):
|
||||
""" Copies skills used by the fit that the character has to clipboard """
|
||||
activeFitID = self.getActiveFit()
|
||||
if activeFitID is None:
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(activeFitID)
|
||||
if fit is None:
|
||||
return
|
||||
|
||||
if not fit.calculated:
|
||||
fit.calculate()
|
||||
|
||||
char = fit.character
|
||||
skillsMap = {}
|
||||
# for thing in itertools.chain(fit.modules, fit.drones, fit.fighters, [fit.ship], fit.appliedImplants, fit.boosters, fit.cargo):
|
||||
for thing in itertools.chain(fit.modules, fit.drones, fit.fighters, fit.appliedImplants, fit.boosters, fit.cargo):
|
||||
self._collectAffectingSkills(thing, char, skillsMap)
|
||||
|
||||
skillsList = ""
|
||||
for skillName in sorted(skillsMap):
|
||||
charLevel = skillsMap[skillName]
|
||||
for level in range(1, charLevel + 1):
|
||||
skillsList += "%s %d\n" % (skillName, level)
|
||||
|
||||
toClipboard(skillsList)
|
||||
|
||||
def _collectAffectingSkills(self, thing, char, skillsMap):
|
||||
""" Collect skills that affect items in the fit that the character has """
|
||||
for attr in ("item", "charge"):
|
||||
if attr == "charge" and isinstance(thing, es_Fighter):
|
||||
continue
|
||||
subThing = getattr(thing, attr, None)
|
||||
if subThing is None:
|
||||
continue
|
||||
if isinstance(thing, es_Fighter) and attr == "charge":
|
||||
continue
|
||||
|
||||
if attr == "charge":
|
||||
cont = getattr(thing, "chargeModifiedAttributes", None)
|
||||
else:
|
||||
cont = getattr(thing, "itemModifiedAttributes", None)
|
||||
|
||||
if cont is not None:
|
||||
for attrName in cont.iterAfflictions():
|
||||
for fit, afflictors in cont.getAfflictions(attrName).items():
|
||||
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
|
||||
if isinstance(afflictor, Skill) and afflictor.character == char:
|
||||
skillName = afflictor.item.name
|
||||
if skillName not in skillsMap:
|
||||
skillsMap[skillName] = afflictor.level
|
||||
elif skillsMap[skillName] < afflictor.level:
|
||||
skillsMap[skillName] = afflictor.level
|
||||
|
||||
def fileImportDialog(self, event):
|
||||
"""Handles importing single/multiple EVE XML / EFT cfg fit files"""
|
||||
with wx.FileDialog(
|
||||
|
||||
@@ -42,6 +42,7 @@ class MainMenuBar(wx.MenuBar):
|
||||
self.graphFrameId = wx.NewId()
|
||||
self.backupFitsId = wx.NewId()
|
||||
self.exportSkillsNeededId = wx.NewId()
|
||||
self.copySkillsNeededId = wx.NewId()
|
||||
self.importCharacterId = wx.NewId()
|
||||
self.exportHtmlId = wx.NewId()
|
||||
self.wikiId = wx.NewId()
|
||||
@@ -57,6 +58,8 @@ class MainMenuBar(wx.MenuBar):
|
||||
self.toggleIgnoreRestrictionID = wx.NewId()
|
||||
self.devToolsId = wx.NewId()
|
||||
self.optimizeFitPrice = wx.NewId()
|
||||
self.copyWithDialogId = wx.NewId()
|
||||
self.copyDirectEftId = wx.NewId()
|
||||
|
||||
self.mainFrame = mainFrame
|
||||
wx.MenuBar.__init__(self)
|
||||
@@ -84,7 +87,8 @@ class MainMenuBar(wx.MenuBar):
|
||||
fitMenu.Append(wx.ID_REDO, _t("&Redo") + "\tCTRL+Y", _t("Redo the most recent undone action"))
|
||||
|
||||
fitMenu.AppendSeparator()
|
||||
fitMenu.Append(wx.ID_COPY, _t("&To Clipboard") + "\tCTRL+C", _t("Export a fit to the clipboard"))
|
||||
fitMenu.Append(self.copyDirectEftId, _t("&To Clipboard (EFT)") + "\tCTRL+C", _t("Export a fit to the clipboard in EFT format"))
|
||||
fitMenu.Append(self.copyWithDialogId, _t("&To Clipboard (Select Format)") + "\tCTRL+SHIFT+C", _t("Export a fit to the clipboard with format selection"))
|
||||
fitMenu.Append(wx.ID_PASTE, _t("&From Clipboard") + "\tCTRL+V", _t("Import a fit from the clipboard"))
|
||||
|
||||
fitMenu.AppendSeparator()
|
||||
@@ -117,6 +121,7 @@ class MainMenuBar(wx.MenuBar):
|
||||
characterMenu.AppendSeparator()
|
||||
characterMenu.Append(self.importCharacterId, _t("&Import Character File"), _t("Import characters into pyfa from file"))
|
||||
characterMenu.Append(self.exportSkillsNeededId, _t("&Export Skills Needed"), _t("Export skills needed for this fitting"))
|
||||
characterMenu.Append(self.copySkillsNeededId, _t("&Copy Skills Needed"), _t("Copy skills needed for this fitting to clipboard"))
|
||||
|
||||
characterMenu.AppendSeparator()
|
||||
characterMenu.Append(self.ssoLoginId, _t("&Manage ESI Characters"))
|
||||
@@ -176,8 +181,10 @@ class MainMenuBar(wx.MenuBar):
|
||||
return
|
||||
enable = activeFitID is not None
|
||||
self.Enable(wx.ID_SAVEAS, enable)
|
||||
self.Enable(wx.ID_COPY, enable)
|
||||
self.Enable(self.copyDirectEftId, enable)
|
||||
self.Enable(self.copyWithDialogId, enable)
|
||||
self.Enable(self.exportSkillsNeededId, enable)
|
||||
self.Enable(self.copySkillsNeededId, enable)
|
||||
|
||||
self.refreshUndo()
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ class MarketBrowser(wx.Panel):
|
||||
self.settings = MarketPriceSettings.getInstance()
|
||||
self.__mode = 'normal'
|
||||
self.__normalBtnMap = {}
|
||||
self.__normalSlotBtnMap = {}
|
||||
self.marketView = MarketTree(self.splitter, self)
|
||||
self.itemView = ItemView(self.splitter, self)
|
||||
|
||||
@@ -64,22 +65,61 @@ class MarketBrowser(wx.Panel):
|
||||
# Same fix as for search box on macs,
|
||||
# need some pixels of extra space or everything clips and is ugly
|
||||
p = wx.Panel(self)
|
||||
box = wx.BoxSizer(wx.HORIZONTAL)
|
||||
p.SetSizer(box)
|
||||
vbox_panel = wx.BoxSizer(wx.VERTICAL)
|
||||
p.SetSizer(vbox_panel)
|
||||
vbox.Add(p, 0, wx.EXPAND)
|
||||
|
||||
# First row: meta buttons
|
||||
metaBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
vbox_panel.Add(metaBox, 0, wx.EXPAND)
|
||||
self.metaButtons = []
|
||||
btn = None
|
||||
for name in list(self.sMkt.META_MAP.keys()):
|
||||
btn = MetaButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT)
|
||||
setattr(self, name, btn)
|
||||
box.Add(btn, 1, wx.ALIGN_CENTER)
|
||||
metaBox.Add(btn, 1, wx.ALIGN_CENTER)
|
||||
btn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleMetaButton)
|
||||
btn.metaName = name
|
||||
self.metaButtons.append(btn)
|
||||
|
||||
# Second row: slot/fits filter buttons (BELOW meta buttons)
|
||||
slotBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
vbox_panel.Add(slotBox, 0, wx.EXPAND)
|
||||
self.slotButtons = []
|
||||
from eos.const import FittingSlot
|
||||
|
||||
# Fits button
|
||||
fitsBtn = MetaButton(p, wx.ID_ANY, "Fits", style=wx.BU_EXACTFIT)
|
||||
setattr(self, "fits", fitsBtn)
|
||||
slotBox.Add(fitsBtn, 1, wx.ALIGN_CENTER)
|
||||
fitsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleSlotButton)
|
||||
fitsBtn.filterType = "fits"
|
||||
# Fits button starts deselected (checkbox, off by default)
|
||||
fitsBtn.setUserSelection(False)
|
||||
self.slotButtons.append(fitsBtn)
|
||||
|
||||
# High, Med, Low, Rig buttons
|
||||
slotMap = {
|
||||
FittingSlot.HIGH: "High",
|
||||
FittingSlot.MED: "Med",
|
||||
FittingSlot.LOW: "Low",
|
||||
FittingSlot.RIG: "Rig"
|
||||
}
|
||||
for slot, label in slotMap.items():
|
||||
slotBtn = MetaButton(p, wx.ID_ANY, label, style=wx.BU_EXACTFIT)
|
||||
setattr(self, "slot_%s" % label.lower(), slotBtn)
|
||||
slotBox.Add(slotBtn, 1, wx.ALIGN_CENTER)
|
||||
slotBtn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleSlotButton)
|
||||
slotBtn.filterType = "slot"
|
||||
slotBtn.slotType = slot
|
||||
# Slot buttons start deselected (unlike meta buttons which start selected)
|
||||
slotBtn.setUserSelection(False)
|
||||
self.slotButtons.append(slotBtn)
|
||||
|
||||
# Make itemview to set toggles according to list contents
|
||||
self.itemView.setToggles()
|
||||
|
||||
p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] + 5))
|
||||
p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] * 2 + 10))
|
||||
|
||||
def toggleMetaButton(self, event):
|
||||
"""Process clicks on toggle buttons"""
|
||||
@@ -100,6 +140,21 @@ class MarketBrowser(wx.Panel):
|
||||
|
||||
self.itemView.filterItemStore()
|
||||
|
||||
def toggleSlotButton(self, event):
|
||||
"""Process clicks on slot/fits filter buttons"""
|
||||
clickedBtn = event.EventObject
|
||||
|
||||
# All buttons (Fits, High, Med, Low, Rig) work as checkboxes (independent toggles)
|
||||
clickedBtn.setUserSelection(clickedBtn.GetValue())
|
||||
self.itemView.filterItemStore()
|
||||
|
||||
def getFitsFilter(self):
|
||||
"""Check if Fits button is active"""
|
||||
for btn in self.slotButtons:
|
||||
if btn.filterType == "fits" and btn.userSelected:
|
||||
return True
|
||||
return False
|
||||
|
||||
def jump(self, item):
|
||||
self.mode = 'normal'
|
||||
self.marketView.jump(item)
|
||||
@@ -141,6 +196,9 @@ class MarketBrowser(wx.Panel):
|
||||
self.__normalBtnMap.clear()
|
||||
for btn in self.metaButtons:
|
||||
self.__normalBtnMap[btn] = btn.userSelected
|
||||
self.__normalSlotBtnMap.clear()
|
||||
for btn in self.slotButtons:
|
||||
self.__normalSlotBtnMap[btn] = btn.userSelected
|
||||
if newMode == 'search':
|
||||
self.marketView.UnselectAll()
|
||||
setting = self.settings.get('marketMGSearchMode')
|
||||
@@ -149,12 +207,16 @@ class MarketBrowser(wx.Panel):
|
||||
if newMode in ('search', 'recent', 'charges'):
|
||||
for btn in self.metaButtons:
|
||||
btn.setUserSelection(True)
|
||||
# Clear slot button selections when searching (search can return any item type)
|
||||
for btn in self.slotButtons:
|
||||
btn.setUserSelection(False)
|
||||
if newMode == 'normal':
|
||||
for btn, state in self.__normalBtnMap.items():
|
||||
btn.setUserSelection(state)
|
||||
for btn, state in self.__normalSlotBtnMap.items():
|
||||
btn.setUserSelection(state)
|
||||
# We turn on all meta buttons permanently
|
||||
if setting == 2:
|
||||
for btn in self.metaButtons:
|
||||
btn.setUserSelection(True)
|
||||
self.__mode = newMode
|
||||
|
||||
|
||||
BIN
imgs/renders/29066@1x.png
Normal file
BIN
imgs/renders/29066@1x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/renders/29066@2x.png
Normal file
BIN
imgs/renders/29066@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
BIN
imgs/renders/29067@1x.png
Normal file
BIN
imgs/renders/29067@1x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
imgs/renders/29067@2x.png
Normal file
BIN
imgs/renders/29067@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
@@ -77,6 +77,14 @@ msgstr ""
|
||||
msgid "&Export Skills Needed"
|
||||
msgstr ""
|
||||
|
||||
#: gui/mainMenuBar.py:120
|
||||
msgid "&Copy Skills Needed"
|
||||
msgstr ""
|
||||
|
||||
#: gui/mainMenuBar.py:120
|
||||
msgid "Copy skills needed for this fitting to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: gui/mainMenuBar.py:66 gui/propertyEditor.py:42
|
||||
msgid "&File"
|
||||
msgstr ""
|
||||
|
||||
BIN
oleacc.dll
LFS
Normal file
BIN
oleacc.dll
LFS
Normal file
Binary file not shown.
28
oleacc.ini
Normal file
28
oleacc.ini
Normal file
@@ -0,0 +1,28 @@
|
||||
;
|
||||
; Pyfa mod Darkmode settings
|
||||
;
|
||||
; ActiveThemePalette [OPTIONAL, Boolean, Default=1]
|
||||
; Indicate which of the available color themes to use. (ex. "ActiveThemePalette = 3" to use the LicoriceBlue color theme)
|
||||
; Available color themes: 1 = BlackBeauty, 2 = Licorice, 3 = LicoriceBlue, 4 = BlackBeauty
|
||||
;
|
||||
; OverrideThemePalette [OPTIONAL, Comma separated string]
|
||||
; Specify a string of (5) comma separated RGB hex colors to use as theme palette, ordered from darkest to lightest.
|
||||
; Valid RGB hex format prefix are #, 0x or nothing.
|
||||
; (ex. OverrideThemePalette = #521ecc, #e68ca1, #1a2070, #a85294, #aaccb5)
|
||||
;
|
||||
; EnableCustomControls[OPTIONAL, Boolean, Default = true]
|
||||
; Indicates if the standard windows controls will be colored by the active theme palette.
|
||||
;
|
||||
; UseExperimentalDarkmode [OPTIONAL, Boolean, Default = true]
|
||||
; Implementation of some undocumented windows api call, to force certain apps into a darkmode state.
|
||||
;
|
||||
; EnableLogging [OPTIONAL, Boolean, Default = false]
|
||||
; Write log output to a .log file.
|
||||
;
|
||||
|
||||
[settings]
|
||||
ActiveThemePalette = 1
|
||||
OverrideThemePalette = #521ecc, #EE3460, #1a2070, #a85294, #aaccb5
|
||||
EnableCustomControls = true
|
||||
UseExperimentalDarkmode = true
|
||||
EnableLogging = false
|
||||
13
pyfa.py
13
pyfa.py
@@ -151,10 +151,15 @@ if __name__ == "__main__":
|
||||
ErrorHandler.SetParent(mf)
|
||||
|
||||
# Start ESI token validation, this helps avoid token expiry
|
||||
from service.esi import Esi
|
||||
esi = Esi.getInstance()
|
||||
esi.startTokenValidation()
|
||||
pyfalog.info("ESI token validation started")
|
||||
try:
|
||||
from service.esi import Esi
|
||||
esi = Esi.getInstance()
|
||||
esi.startTokenValidation()
|
||||
pyfalog.info("ESI token validation started")
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.warning(f"failed to start ESI token validation thread:\n{e}")
|
||||
|
||||
if options.profile_path:
|
||||
profile_path = os.path.join(options.profile_path, 'pyfa-{}.profile'.format(datetime.datetime.now().strftime('%Y%m%d_%H%M%S')))
|
||||
|
||||
37
pyfa.spec
37
pyfa.spec
@@ -79,8 +79,20 @@ a = Analysis(['pyfa.py'],
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
a_headless = Analysis(['pyfa_headless.py'],
|
||||
pathex=pathex,
|
||||
binaries=[],
|
||||
datas=added_files,
|
||||
hiddenimports=import_these,
|
||||
hookspath=['dist_assets/pyinstaller_hooks'],
|
||||
runtime_hooks=[],
|
||||
excludes=['Tkinter'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
pyz_headless = PYZ(a_headless.pure, a_headless.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
@@ -96,6 +108,19 @@ exe = EXE(
|
||||
contents_directory='app',
|
||||
)
|
||||
|
||||
# Headless: server only. POST /simulate on port 9123.
|
||||
exe_headless = EXE(
|
||||
pyz_headless,
|
||||
a_headless.scripts,
|
||||
exclude_binaries=True,
|
||||
name='pyfa-headless',
|
||||
debug=debug,
|
||||
strip=False,
|
||||
upx=upx,
|
||||
console=True,
|
||||
contents_directory='app',
|
||||
)
|
||||
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
@@ -106,6 +131,16 @@ coll = COLLECT(
|
||||
name='pyfa',
|
||||
)
|
||||
|
||||
coll_headless = COLLECT(
|
||||
exe_headless,
|
||||
a_headless.binaries,
|
||||
a_headless.zipfiles,
|
||||
a_headless.datas,
|
||||
strip=False,
|
||||
upx=upx,
|
||||
name='pyfa_headless',
|
||||
)
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
info_plist = {
|
||||
'NSHighResolutionCapable': 'True',
|
||||
|
||||
5
pyfa_headless.py
Normal file
5
pyfa_headless.py
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Headless sim daemon only. POST /simulate with JSON body.
|
||||
from scripts.pyfa_cli_stats import _run_http_server
|
||||
|
||||
_run_http_server(9123, None)
|
||||
27
pyproject.toml
Normal file
27
pyproject.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[project]
|
||||
name = "pyfa"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"beautifulsoup4==4.12.2",
|
||||
"cryptography==42.0.4",
|
||||
"logbook==1.7.0.post0",
|
||||
"markdown2==2.4.11",
|
||||
"matplotlib==3.8.2",
|
||||
"numpy==1.26.2",
|
||||
"packaging==23.2",
|
||||
"python-dateutil==2.8.2",
|
||||
"python-jose==3.3.0",
|
||||
"pyyaml==6.0.1",
|
||||
"requests==2.31.0",
|
||||
"requests-cache==1.1.1",
|
||||
"roman==4.1",
|
||||
"ruff>=0.14.8",
|
||||
"sqlalchemy==1.4.50",
|
||||
"wxpython==4.2.1",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
||||
60
release.sh
Normal file
60
release.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Figuring out the tag..."
|
||||
TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
|
||||
if [ -z "$TAG" ]; then
|
||||
# Get the latest tag
|
||||
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "v0.0.0")
|
||||
# Remove 'v' prefix if present
|
||||
LATEST_TAG=${LATEST_TAG#v}
|
||||
# Increment the patch version
|
||||
IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_TAG"
|
||||
VERSION_PARTS[2]=$((VERSION_PARTS[2]+1))
|
||||
TAG="v${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}"
|
||||
# Create a new tag
|
||||
git tag $TAG
|
||||
git push origin $TAG
|
||||
fi
|
||||
echo "Tag: $TAG"
|
||||
|
||||
echo "Building the binary..."
|
||||
./build.sh
|
||||
|
||||
echo "Creating release zip..."
|
||||
ZIP="pyfa-${TAG}-win.zip"
|
||||
7z a "${ZIP}" dist/pyfa/*
|
||||
|
||||
echo "Creating a release..."
|
||||
TOKEN="$GITEA_API_KEY"
|
||||
GITEA="https://git.site.quack-lab.dev"
|
||||
REPO="dave/pyfa"
|
||||
|
||||
# Create a release
|
||||
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tag_name": "'"$TAG"'",
|
||||
"name": "'"$TAG"'",
|
||||
"draft": false,
|
||||
"prerelease": false
|
||||
}' \
|
||||
$GITEA/api/v1/repos/$REPO/releases)
|
||||
|
||||
# Extract the release ID
|
||||
echo $RELEASE_RESPONSE
|
||||
RELEASE_ID=$(echo $RELEASE_RESPONSE | awk -F'"id":' '{print $2+0; exit}')
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
|
||||
echo "Uploading the zip..."
|
||||
curl -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@${ZIP}" \
|
||||
"$GITEA/api/v1/repos/$REPO/releases/${RELEASE_ID}/assets?name=${ZIP}"
|
||||
|
||||
rm "${ZIP}"
|
||||
|
||||
echo "Release complete! ${ZIP} uploaded to ${TAG}"
|
||||
@@ -41,11 +41,15 @@ def main(old, new, groups=True, effects=True, attributes=True, renames=True):
|
||||
new_cursor = new_db.cursor()
|
||||
|
||||
# Force some of the items to make them published
|
||||
FORCEPUB_TYPES = ("Ibis", "Impairor", "Velator", "Reaper",
|
||||
"Amarr Tactical Destroyer Propulsion Mode",
|
||||
"Amarr Tactical Destroyer Sharpshooter Mode",
|
||||
"Amarr Tactical Destroyer Defense Mode")
|
||||
OVERRIDES_TYPEPUB = 'UPDATE invtypes SET published = 1 WHERE typeName = ?'
|
||||
FORCEPUB_TYPES = (
|
||||
"% Propulsion Mode",
|
||||
"% Sharpshooter Mode",
|
||||
"% Defense Mode",
|
||||
"% Primary Mode",
|
||||
"% Secondary Mode",
|
||||
"% Tertiary Mode",
|
||||
)
|
||||
OVERRIDES_TYPEPUB = 'UPDATE invtypes SET published = 1 WHERE typeName like ?'
|
||||
for typename in FORCEPUB_TYPES:
|
||||
old_cursor.execute(OVERRIDES_TYPEPUB, (typename,))
|
||||
new_cursor.execute(OVERRIDES_TYPEPUB, (typename,))
|
||||
|
||||
414
scripts/pyfa_cli_stats.py
Normal file
414
scripts/pyfa_cli_stats.py
Normal file
@@ -0,0 +1,414 @@
|
||||
import json
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
import config
|
||||
import eos.config
|
||||
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.utils.spoolSupport import SpoolOptions, SpoolType
|
||||
|
||||
|
||||
def _init_pyfa(savepath: str | None) -> None:
|
||||
config.debug = False
|
||||
config.loggingLevel = config.LOGLEVEL_MAP["error"]
|
||||
config.defPaths(savepath)
|
||||
config.defLogging()
|
||||
import eos.db # noqa: F401
|
||||
eos.db.saveddata_meta.create_all() # type: ignore[name-defined]
|
||||
import eos.events # noqa: F401
|
||||
from service import prefetch # noqa: F401
|
||||
|
||||
|
||||
def _parse_drone_markers(text: str) -> tuple[str, set[str]]:
|
||||
active_names: set[str] = set()
|
||||
cleaned_lines: list[str] = []
|
||||
for raw_line in text.splitlines():
|
||||
line = raw_line.rstrip()
|
||||
if " xx" in line:
|
||||
idx = line.find(" xx")
|
||||
marker_segment = line[idx + 1 :].strip()
|
||||
|
||||
if marker_segment == "xx":
|
||||
payload = line[:idx].rstrip()
|
||||
parts = payload.split(" x", 1)
|
||||
if len(parts) == 2:
|
||||
type_name = parts[0].strip()
|
||||
if type_name:
|
||||
active_names.add(type_name)
|
||||
line = payload
|
||||
|
||||
cleaned_lines.append(line)
|
||||
|
||||
return "\n".join(cleaned_lines), active_names
|
||||
|
||||
|
||||
def _import_single_fit(raw_text: str) -> tuple[object, set[str]]:
|
||||
from service.port.port import Port
|
||||
|
||||
cleaned_text, active_names = _parse_drone_markers(raw_text)
|
||||
import_type, import_data = Port.importFitFromBuffer(cleaned_text)
|
||||
|
||||
if not import_data or len(import_data) != 1:
|
||||
raise ValueError("Expected exactly one fit in input; got %d" % (len(import_data) if import_data else 0))
|
||||
|
||||
fit = import_data[0]
|
||||
|
||||
if active_names:
|
||||
for drone in fit.drones:
|
||||
if getattr(drone.item, "typeName", None) in active_names:
|
||||
drone.amountActive = drone.amount
|
||||
for fighter in fit.fighters:
|
||||
if getattr(fighter.item, "typeName", None) in active_names:
|
||||
fighter.amount = fighter.amount
|
||||
|
||||
return fit, active_names
|
||||
|
||||
|
||||
def _add_projected_fit(s_fit, target_fit, projected_fit, amount: int) -> None:
|
||||
fit = s_fit.getFit(target_fit.ID)
|
||||
projected = s_fit.getFit(projected_fit.ID, projected=True)
|
||||
if projected is None:
|
||||
raise ValueError("Projected fit %s is not available" % projected_fit.ID)
|
||||
|
||||
if projected in fit.projectedFits and projected.ID in fit.projectedFitDict:
|
||||
projection_info = projected.getProjectionInfo(fit.ID)
|
||||
if projection_info is None:
|
||||
raise ValueError("Projection info missing for projected fit %s" % projected_fit.ID)
|
||||
else:
|
||||
fit.projectedFitDict[projected.ID] = projected
|
||||
eos.db.saveddata_session.flush()
|
||||
eos.db.saveddata_session.refresh(projected)
|
||||
projection_info = projected.getProjectionInfo(fit.ID)
|
||||
if projection_info is None:
|
||||
raise ValueError("Projection info missing after linking projected fit %s" % projected_fit.ID)
|
||||
|
||||
projection_info.amount = amount
|
||||
projection_info.active = True
|
||||
|
||||
|
||||
def _add_command_fit(s_fit, target_fit, command_fit) -> None:
|
||||
fit = s_fit.getFit(target_fit.ID)
|
||||
command = s_fit.getFit(command_fit.ID)
|
||||
if command is None:
|
||||
raise ValueError("Command fit %s is not available" % command_fit.ID)
|
||||
|
||||
if command in fit.commandFits or command.ID in fit.commandFitDict:
|
||||
return
|
||||
|
||||
fit.commandFitDict[command.ID] = command
|
||||
eos.db.saveddata_session.flush()
|
||||
eos.db.saveddata_session.refresh(command)
|
||||
info = command.getCommandInfo(fit.ID)
|
||||
if info is None:
|
||||
raise ValueError("Command info missing for command fit %s" % command_fit.ID)
|
||||
info.active = True
|
||||
|
||||
|
||||
def _collect_resources(fit) -> dict:
|
||||
ship = fit.ship
|
||||
resources: dict = {}
|
||||
|
||||
resources["hardpoints"] = {
|
||||
"turret": {
|
||||
"used": fit.getHardpointsUsed(FittingHardpoint.TURRET),
|
||||
"total": ship.getModifiedItemAttr("turretSlotsLeft"),
|
||||
},
|
||||
"launcher": {
|
||||
"used": fit.getHardpointsUsed(FittingHardpoint.MISSILE),
|
||||
"total": ship.getModifiedItemAttr("launcherSlotsLeft"),
|
||||
},
|
||||
}
|
||||
|
||||
resources["drones"] = {
|
||||
"active": fit.activeDrones,
|
||||
"max_active": fit.extraAttributes.get("maxActiveDrones"),
|
||||
"bay_used": fit.droneBayUsed,
|
||||
"bay_capacity": ship.getModifiedItemAttr("droneCapacity"),
|
||||
"bandwidth_used": fit.droneBandwidthUsed,
|
||||
"bandwidth_capacity": ship.getModifiedItemAttr("droneBandwidth"),
|
||||
}
|
||||
|
||||
resources["fighters"] = {
|
||||
"tubes_used": fit.fighterTubesUsed,
|
||||
"tubes_total": fit.fighterTubesTotal,
|
||||
"bay_used": fit.fighterBayUsed,
|
||||
"bay_capacity": ship.getModifiedItemAttr("fighterCapacity"),
|
||||
}
|
||||
|
||||
resources["calibration"] = {
|
||||
"used": fit.calibrationUsed,
|
||||
"total": ship.getModifiedItemAttr("upgradeCapacity"),
|
||||
}
|
||||
|
||||
resources["powergrid"] = {
|
||||
"used": fit.pgUsed,
|
||||
"output": ship.getModifiedItemAttr("powerOutput"),
|
||||
}
|
||||
|
||||
resources["cpu"] = {
|
||||
"used": fit.cpuUsed,
|
||||
"output": ship.getModifiedItemAttr("cpuOutput"),
|
||||
}
|
||||
|
||||
resources["cargo"] = {
|
||||
"used": fit.cargoBayUsed,
|
||||
"capacity": ship.getModifiedItemAttr("capacity"),
|
||||
}
|
||||
|
||||
return resources
|
||||
|
||||
|
||||
def _collect_defense(fit) -> dict:
|
||||
ship = fit.ship
|
||||
|
||||
def _res(attr_name):
|
||||
return ship.getModifiedItemAttr(attr_name)
|
||||
|
||||
resonance = {
|
||||
"armor": {"em": _res("armorEmDamageResonance"), "exp": _res("armorExplosiveDamageResonance"), "kin": _res("armorKineticDamageResonance"), "therm": _res("armorThermalDamageResonance")},
|
||||
"shield": {"em": _res("shieldEmDamageResonance"), "exp": _res("shieldExplosiveDamageResonance"), "kin": _res("shieldKineticDamageResonance"), "therm": _res("shieldThermalDamageResonance")},
|
||||
"hull": {"em": _res("emDamageResonance"), "exp": _res("explosiveDamageResonance"), "kin": _res("kineticDamageResonance"), "therm": _res("thermalDamageResonance")},
|
||||
}
|
||||
defense: dict = {}
|
||||
defense["hp"] = fit.hp
|
||||
defense["ehp"] = fit.ehp
|
||||
defense["resonance"] = resonance
|
||||
defense["tank"] = fit.tank
|
||||
defense["effective_tank"] = fit.effectiveTank
|
||||
defense["sustainable_tank"] = fit.sustainableTank
|
||||
defense["effective_sustainable_tank"] = fit.effectiveSustainableTank
|
||||
return defense
|
||||
|
||||
|
||||
def _collect_capacitor(fit) -> dict:
|
||||
ship = fit.ship
|
||||
cap: dict = {}
|
||||
cap["capacity"] = ship.getModifiedItemAttr("capacitorCapacity")
|
||||
cap["recharge"] = fit.capRecharge
|
||||
cap["use"] = fit.capUsed
|
||||
cap["delta"] = fit.capDelta
|
||||
cap["stable"] = fit.capStable
|
||||
cap["state"] = fit.capState
|
||||
cap["neutralizer_resistance"] = ship.getModifiedItemAttr("energyWarfareResistance", 1)
|
||||
return cap
|
||||
|
||||
|
||||
def _collect_firepower(fit) -> dict:
|
||||
default_spool = eos.config.settings["globalDefaultSpoolupPercentage"]
|
||||
spool = SpoolOptions(SpoolType.SPOOL_SCALE, default_spool, False)
|
||||
wdps = fit.getWeaponDps(spoolOptions=spool)
|
||||
ddps = fit.getDroneDps()
|
||||
wvol = fit.getWeaponVolley(spoolOptions=spool)
|
||||
dvol = fit.getDroneVolley()
|
||||
return {
|
||||
"weapon_dps": wdps.total,
|
||||
"drone_dps": ddps.total,
|
||||
"total_dps": fit.getTotalDps(spoolOptions=spool).total,
|
||||
"weapon_volley": wvol.total,
|
||||
"drone_volley": dvol.total,
|
||||
"total_volley": fit.getTotalVolley(spoolOptions=spool).total,
|
||||
"weapons": [],
|
||||
}
|
||||
|
||||
|
||||
def _collect_remote_reps(fit) -> dict:
|
||||
default_spool = eos.config.settings["globalDefaultSpoolupPercentage"]
|
||||
pre = fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True))
|
||||
full = fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True))
|
||||
current = fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, default_spool, False))
|
||||
return {
|
||||
"current": {
|
||||
"capacitor": current.capacitor,
|
||||
"shield": current.shield,
|
||||
"armor": current.armor,
|
||||
"hull": current.hull,
|
||||
},
|
||||
"pre_spool": {
|
||||
"capacitor": pre.capacitor,
|
||||
"shield": pre.shield,
|
||||
"armor": pre.armor,
|
||||
"hull": pre.hull,
|
||||
},
|
||||
"full_spool": {
|
||||
"capacitor": full.capacitor,
|
||||
"shield": full.shield,
|
||||
"armor": full.armor,
|
||||
"hull": full.hull,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _collect_targeting_misc(fit) -> dict:
|
||||
ship = fit.ship
|
||||
misc: dict = {}
|
||||
misc["targets_max"] = fit.maxTargets
|
||||
misc["target_range"] = fit.maxTargetRange
|
||||
misc["scan_resolution"] = ship.getModifiedItemAttr("scanResolution")
|
||||
misc["scan_strength"] = fit.scanStrength
|
||||
misc["scan_type"] = fit.scanType
|
||||
misc["jam_chance"] = fit.jamChance
|
||||
misc["drone_control_range"] = fit.extraAttributes.get("droneControlRange")
|
||||
misc["speed"] = fit.maxSpeed
|
||||
misc["align_time"] = fit.alignTime
|
||||
misc["signature_radius"] = ship.getModifiedItemAttr("signatureRadius")
|
||||
misc["warp_speed"] = fit.warpSpeed
|
||||
misc["max_warp_distance"] = fit.maxWarpDistance
|
||||
misc["probe_size"] = fit.probeSize
|
||||
misc["cargo_capacity"] = ship.getModifiedItemAttr("capacity")
|
||||
misc["cargo_used"] = fit.cargoBayUsed
|
||||
return misc
|
||||
|
||||
|
||||
def _collect_price(fit) -> dict:
|
||||
ship_price = 0.0
|
||||
module_price = 0.0
|
||||
drone_price = 0.0
|
||||
fighter_price = 0.0
|
||||
cargo_price = 0.0
|
||||
booster_price = 0.0
|
||||
implant_price = 0.0
|
||||
|
||||
if fit:
|
||||
ship_price = getattr(fit.ship.item.price, "price", 0.0)
|
||||
|
||||
for module in fit.modules:
|
||||
if not module.isEmpty:
|
||||
module_price += getattr(module.item.price, "price", 0.0)
|
||||
|
||||
for drone in fit.drones:
|
||||
drone_price += getattr(drone.item.price, "price", 0.0) * drone.amount
|
||||
|
||||
for fighter in fit.fighters:
|
||||
fighter_price += getattr(fighter.item.price, "price", 0.0) * fighter.amount
|
||||
|
||||
for cargo in fit.cargo:
|
||||
cargo_price += getattr(cargo.item.price, "price", 0.0) * cargo.amount
|
||||
|
||||
for booster in fit.boosters:
|
||||
booster_price += getattr(booster.item.price, "price", 0.0)
|
||||
|
||||
for implant in fit.appliedImplants:
|
||||
implant_price += getattr(implant.item.price, "price", 0.0)
|
||||
|
||||
total_price = ship_price + module_price + drone_price + fighter_price + cargo_price + booster_price + implant_price
|
||||
|
||||
return {
|
||||
"ship": ship_price,
|
||||
"fittings": module_price,
|
||||
"drones_and_fighters": drone_price + fighter_price,
|
||||
"cargo": cargo_price,
|
||||
"character": booster_price + implant_price,
|
||||
"total": total_price,
|
||||
}
|
||||
|
||||
|
||||
def _build_output(main_fit) -> dict:
|
||||
return {
|
||||
"fit": {
|
||||
"id": main_fit.ID,
|
||||
"name": main_fit.name,
|
||||
"ship_type": main_fit.ship.item.typeName,
|
||||
},
|
||||
"resources": _collect_resources(main_fit),
|
||||
"defense": _collect_defense(main_fit),
|
||||
"capacitor": _collect_capacitor(main_fit),
|
||||
"firepower": _collect_firepower(main_fit),
|
||||
"remote_reps_outgoing": _collect_remote_reps(main_fit),
|
||||
"targeting_misc": _collect_targeting_misc(main_fit),
|
||||
"price": _collect_price(main_fit),
|
||||
}
|
||||
|
||||
|
||||
def compute_stats(payload: dict, savepath: str | None = None) -> dict:
|
||||
if "fit" not in payload or not isinstance(payload["fit"], str):
|
||||
raise ValueError("Payload must contain a 'fit' field with EFT/text export")
|
||||
_init_pyfa(savepath)
|
||||
from service.fit import Fit as FitService
|
||||
s_fit = FitService.getInstance()
|
||||
if s_fit.character is None:
|
||||
from eos.saveddata.character import Character as saveddata_Character
|
||||
s_fit.character = saveddata_Character.getAll5()
|
||||
main_fit, _ = _import_single_fit(payload["fit"])
|
||||
projected_defs = payload.get("projected_fits", [])
|
||||
command_defs = payload.get("command_fits", [])
|
||||
projected_fits: list[tuple[object, int]] = []
|
||||
for entry in projected_defs:
|
||||
if not isinstance(entry, dict):
|
||||
raise ValueError("Each projected_fits entry must be an object")
|
||||
fit_text = entry.get("fit")
|
||||
count = entry.get("count")
|
||||
if not isinstance(fit_text, str):
|
||||
raise ValueError("Each projected_fits entry must contain a string 'fit'")
|
||||
if not isinstance(count, int) or count <= 0:
|
||||
raise ValueError("Each projected_fits entry must contain a positive integer 'count'")
|
||||
pf, _ = _import_single_fit(fit_text)
|
||||
projected_fits.append((pf, count))
|
||||
command_fits: list[object] = []
|
||||
for entry in command_defs:
|
||||
if not isinstance(entry, dict):
|
||||
raise ValueError("Each command_fits entry must be an object")
|
||||
fit_text = entry.get("fit")
|
||||
if not isinstance(fit_text, str):
|
||||
raise ValueError("Each command_fits entry must contain a string 'fit'")
|
||||
cf, _ = _import_single_fit(fit_text)
|
||||
command_fits.append(cf)
|
||||
for pf, count in projected_fits:
|
||||
_add_projected_fit(s_fit, main_fit, pf, count)
|
||||
for cf in command_fits:
|
||||
_add_command_fit(s_fit, main_fit, cf)
|
||||
s_fit.recalc(main_fit)
|
||||
s_fit.fill(main_fit)
|
||||
return _build_output(main_fit)
|
||||
|
||||
|
||||
def _run_http_server(port: int, savepath: str | None) -> None:
|
||||
class SimulateHandler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
if self.path != "/simulate":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length).decode("utf-8")
|
||||
try:
|
||||
payload = json.loads(body)
|
||||
except json.JSONDecodeError as exc:
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(("Invalid JSON: %s" % exc).encode("utf-8"))
|
||||
return
|
||||
if not isinstance(payload, dict):
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Top-level JSON must be an object")
|
||||
return
|
||||
try:
|
||||
output = compute_stats(payload, savepath)
|
||||
except ValueError as e:
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(str(e).encode("utf-8"))
|
||||
return
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(str(e).encode("utf-8"))
|
||||
return
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(output, indent=2, sort_keys=True).encode("utf-8"))
|
||||
self.wfile.write(b"\n")
|
||||
|
||||
with HTTPServer(("", port), SimulateHandler) as httpd:
|
||||
print("POST /simulate on http://127.0.0.1:%s" % port, file=sys.stderr, flush=True)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_run_http_server(9123, None)
|
||||
@@ -13,11 +13,9 @@ from service.const import EsiLoginMethod, EsiSsoMode
|
||||
from eos.saveddata.ssocharacter import SsoCharacter
|
||||
from service.esiAccess import APIException, GenericSsoError
|
||||
import gui.globalEvents as GE
|
||||
from gui.ssoLogin import SsoLogin
|
||||
from service.server import StoppableHTTPServer, AuthHandler
|
||||
from service.settings import EsiSettings
|
||||
from service.esiAccess import EsiAccess
|
||||
import gui.mainFrame
|
||||
|
||||
from requests import Session
|
||||
|
||||
@@ -140,6 +138,7 @@ class Esi(EsiAccess):
|
||||
self.fittings_deleted.add(fittingID)
|
||||
|
||||
def login(self):
|
||||
import gui.ssoLogin
|
||||
start_server = self.settings.get('loginMode') == EsiLoginMethod.SERVER and self.server_base.supports_auto_login
|
||||
with gui.ssoLogin.SsoLogin(self.server_base, start_server) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
@@ -327,6 +327,8 @@ class Market:
|
||||
"Sidewinder" : self.les_grp, # AT20 prize
|
||||
"Cobra" : self.les_grp, # AT20 prize
|
||||
"Python" : self.les_grp, # AT20 prize
|
||||
"Skua" : self.les_grp, # AT21 prize
|
||||
"Anhinga" : self.les_grp, # AT21 prize
|
||||
}
|
||||
|
||||
self.ITEMS_FORCEGROUP_R = self.__makeRevDict(self.ITEMS_FORCEGROUP)
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
from .efs import EfsPort
|
||||
from .port import Port
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "Port":
|
||||
from service.port.port import Port
|
||||
return Port
|
||||
raise AttributeError("module %r has no attribute %r" % (__name__, name))
|
||||
|
||||
@@ -31,7 +31,6 @@ from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
from service.const import PortDnaOptions
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
@@ -80,6 +79,7 @@ def importDnaAlt(string, fitName=None):
|
||||
return processImportInfo(info, fitName, "*")
|
||||
|
||||
def processImportInfo(info, fitName, amountSeparator):
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
sMkt = Market.getInstance()
|
||||
f = Fit()
|
||||
try:
|
||||
|
||||
@@ -16,10 +16,6 @@ from eos.effectHandlerHelpers import HandledList
|
||||
from eos.db import gamedata_session, getCategory, getAttributeInfo, getGroup
|
||||
from eos.gamedata import Attribute, Effect, Group, Item, ItemEffect
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
from gui.fitCommands.calc.module.localAdd import CalcAddLocalModuleCommand
|
||||
from gui.fitCommands.calc.module.localRemove import CalcRemoveLocalModulesCommand
|
||||
from gui.fitCommands.calc.module.changeCharges import CalcChangeModuleChargesCommand
|
||||
from gui.fitCommands.helpers import ModuleInfo
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -68,6 +64,9 @@ class EfsPort:
|
||||
|
||||
if propID is None:
|
||||
return None
|
||||
from gui.fitCommands.calc.module.localAdd import CalcAddLocalModuleCommand
|
||||
from gui.fitCommands.calc.module.localRemove import CalcRemoveLocalModulesCommand
|
||||
from gui.fitCommands.helpers import ModuleInfo
|
||||
cmd = CalcAddLocalModuleCommand(fitID, ModuleInfo(itemID=propID))
|
||||
cmd.Do()
|
||||
if cmd.needsGuiRecalc:
|
||||
@@ -137,6 +136,7 @@ class EfsPort:
|
||||
EfsPort.attrDirectMap(["reloadTime"], stats, mod)
|
||||
c = mod.charge
|
||||
if c:
|
||||
from gui.fitCommands.calc.module.changeCharges import CalcChangeModuleChargesCommand
|
||||
sFit.recalc(fit)
|
||||
CalcChangeModuleChargesCommand(
|
||||
fit.ID,
|
||||
|
||||
@@ -33,7 +33,6 @@ from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
from service.const import PortEftOptions
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
@@ -241,6 +240,7 @@ def exportCargo(cargos):
|
||||
|
||||
|
||||
def importEft(lines):
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
lines = _importPrepare(lines)
|
||||
try:
|
||||
fit = _importCreateFit(lines)
|
||||
@@ -877,6 +877,7 @@ class AbstractFit:
|
||||
self.getContainerBySlot(m.slot).append(m)
|
||||
|
||||
def __makeModule(self, itemSpec):
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
# Mutate item if needed
|
||||
m = None
|
||||
if itemSpec.mutationIdx in self.mutations:
|
||||
|
||||
@@ -31,7 +31,6 @@ from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
|
||||
@@ -161,6 +160,7 @@ def exportESI(ofit, exportCharges, exportImplants, exportBoosters, callback):
|
||||
|
||||
|
||||
def importESI(string):
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
|
||||
sMkt = Market.getInstance()
|
||||
fitobj = Fit()
|
||||
|
||||
@@ -195,10 +195,14 @@ class Port:
|
||||
# TODO: catch the exception?
|
||||
# activeFit is reserved?, bufferStr is unicode? (assume only clipboard string?
|
||||
sFit = svcFit.getInstance()
|
||||
if sFit.character is None:
|
||||
from eos.saveddata.character import Character as saveddata_Character
|
||||
sFit.character = saveddata_Character.getAll5()
|
||||
importType, makesNewFits, importData = Port.importAuto(bufferStr, activeFit=activeFit)
|
||||
|
||||
if makesNewFits:
|
||||
for fit in importData:
|
||||
fits = [f for f in importData if f is not None]
|
||||
for fit in fits:
|
||||
fit.character = sFit.character
|
||||
fit.damagePattern = sFit.pattern
|
||||
fit.targetProfile = sFit.targetProfile
|
||||
@@ -208,6 +212,7 @@ class Port:
|
||||
useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
|
||||
fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
|
||||
db.save(fit)
|
||||
return importType, fits
|
||||
return importType, importData
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -32,7 +32,6 @@ from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
from service.fit import Fit as svcFit
|
||||
from service.market import Market
|
||||
from service.port.muta import renderMutantAttrs, parseMutantAttrs
|
||||
@@ -155,6 +154,7 @@ def _resolve_module(hardware, sMkt, b_localized):
|
||||
|
||||
|
||||
def importXml(text, progress):
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
from .port import Port
|
||||
sMkt = Market.getInstance()
|
||||
doc = xml.dom.minidom.parseString(text)
|
||||
|
||||
@@ -49168,6 +49168,40 @@
|
||||
"stackable": 1,
|
||||
"unitID": 105
|
||||
},
|
||||
"6054": {
|
||||
"attributeID": 6054,
|
||||
"categoryID": 37,
|
||||
"dataType": 5,
|
||||
"defaultValue": 0.0,
|
||||
"displayWhenZero": 0,
|
||||
"highIsGood": 1,
|
||||
"name": "AnhingaLargeMissilePowerFittingBonus",
|
||||
"published": 0,
|
||||
"stackable": 1
|
||||
},
|
||||
"6055": {
|
||||
"attributeID": 6055,
|
||||
"categoryID": 37,
|
||||
"dataType": 5,
|
||||
"defaultValue": 0.0,
|
||||
"displayWhenZero": 0,
|
||||
"highIsGood": 1,
|
||||
"name": "AnhingaLargeMissileCpuFittingBonus",
|
||||
"published": 0,
|
||||
"stackable": 1
|
||||
},
|
||||
"6057": {
|
||||
"attributeID": 6057,
|
||||
"categoryID": 2,
|
||||
"dataType": 5,
|
||||
"defaultValue": 1.0,
|
||||
"description": "",
|
||||
"displayWhenZero": 0,
|
||||
"highIsGood": 0,
|
||||
"name": "modeShieldRechargePostDiv",
|
||||
"published": 0,
|
||||
"stackable": 1
|
||||
},
|
||||
"6062": {
|
||||
"attributeID": 6062,
|
||||
"categoryID": 51,
|
||||
|
||||
@@ -58317,6 +58317,7 @@
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeAttributeID": 54,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"6216": {
|
||||
@@ -99669,6 +99670,56 @@
|
||||
"published": 0,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12758": {
|
||||
"description_de": "Automatically generated effect",
|
||||
"description_en-us": "Automatically generated effect",
|
||||
"description_es": "Automatically generated effect",
|
||||
"description_fr": "Automatically generated effect",
|
||||
"description_it": "Automatically generated effect",
|
||||
"description_ja": "Automatically generated effect",
|
||||
"description_ko": "Automatically generated effect",
|
||||
"description_ru": "Automatically generated effect",
|
||||
"description_zh": "Automatically generated effect",
|
||||
"descriptionID": 1022818,
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
"effectID": 12758,
|
||||
"effectName": "shipRoleBonusAnhingaLargeMissilePowerFittingBonus",
|
||||
"electronicChance": 0,
|
||||
"isAssistance": 0,
|
||||
"isOffensive": 0,
|
||||
"isWarpSafe": 0,
|
||||
"modifierInfo": [
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationGroupModifier",
|
||||
"groupID": 1245,
|
||||
"modifiedAttributeID": 30,
|
||||
"modifyingAttributeID": 6054,
|
||||
"operation": 0
|
||||
},
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationGroupModifier",
|
||||
"groupID": 506,
|
||||
"modifiedAttributeID": 30,
|
||||
"modifyingAttributeID": 6054,
|
||||
"operation": 0
|
||||
},
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationGroupModifier",
|
||||
"groupID": 508,
|
||||
"modifiedAttributeID": 30,
|
||||
"modifyingAttributeID": 6054,
|
||||
"operation": 0
|
||||
}
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeAttributeID": 54,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12759": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 4,
|
||||
@@ -99770,6 +99821,131 @@
|
||||
"published": 0,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12764": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
"effectID": 12764,
|
||||
"effectName": "shipRoleBonusAnhingaLargeMissileCpuFittingBonus",
|
||||
"electronicChance": 0,
|
||||
"isAssistance": 0,
|
||||
"isOffensive": 0,
|
||||
"isWarpSafe": 0,
|
||||
"modifierInfo": [
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationGroupModifier",
|
||||
"groupID": 1245,
|
||||
"modifiedAttributeID": 50,
|
||||
"modifyingAttributeID": 6055,
|
||||
"operation": 0
|
||||
},
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationGroupModifier",
|
||||
"groupID": 506,
|
||||
"modifiedAttributeID": 50,
|
||||
"modifyingAttributeID": 6055,
|
||||
"operation": 0
|
||||
},
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationGroupModifier",
|
||||
"groupID": 508,
|
||||
"modifiedAttributeID": 50,
|
||||
"modifyingAttributeID": 6055,
|
||||
"operation": 0
|
||||
}
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeAttributeID": 54,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12765": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
"effectID": 12765,
|
||||
"effectName": "shipBonusTorpedoAndCruiseMissileExplosionVelocityMB",
|
||||
"electronicChance": 0,
|
||||
"isAssistance": 0,
|
||||
"isOffensive": 0,
|
||||
"isWarpSafe": 0,
|
||||
"modifierInfo": [
|
||||
{
|
||||
"domain": "charID",
|
||||
"func": "OwnerRequiredSkillModifier",
|
||||
"modifiedAttributeID": 653,
|
||||
"modifyingAttributeID": 490,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3325
|
||||
},
|
||||
{
|
||||
"domain": "charID",
|
||||
"func": "OwnerRequiredSkillModifier",
|
||||
"modifiedAttributeID": 653,
|
||||
"modifyingAttributeID": 490,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3326
|
||||
}
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12766": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
"effectID": 12766,
|
||||
"effectName": "shipBonusTorpedoAndCruiseMissileExplosionRadiusCBC1",
|
||||
"electronicChance": 0,
|
||||
"isAssistance": 0,
|
||||
"isOffensive": 0,
|
||||
"isWarpSafe": 0,
|
||||
"modifierInfo": [
|
||||
{
|
||||
"domain": "charID",
|
||||
"func": "OwnerRequiredSkillModifier",
|
||||
"modifiedAttributeID": 654,
|
||||
"modifyingAttributeID": 743,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3326
|
||||
},
|
||||
{
|
||||
"domain": "charID",
|
||||
"func": "OwnerRequiredSkillModifier",
|
||||
"modifiedAttributeID": 654,
|
||||
"modifyingAttributeID": 743,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3325
|
||||
}
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12767": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
"effectID": 12767,
|
||||
"effectName": "tacticalBonusSkuaDefensiveShieldRechargeRate",
|
||||
"electronicChance": 0,
|
||||
"guid": "",
|
||||
"isAssistance": 0,
|
||||
"isOffensive": 0,
|
||||
"isWarpSafe": 0,
|
||||
"modifierInfo": [
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "ItemModifier",
|
||||
"modifiedAttributeID": 479,
|
||||
"modifyingAttributeID": 6057,
|
||||
"operation": 5
|
||||
}
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12771": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
@@ -99861,5 +100037,68 @@
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12777": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
"effectID": 12777,
|
||||
"effectName": "roleBonusCDLinksPGCPUReductionSkua",
|
||||
"electronicChance": 0,
|
||||
"isAssistance": 0,
|
||||
"isOffensive": 0,
|
||||
"isWarpSafe": 0,
|
||||
"modifierInfo": [
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationRequiredSkillModifier",
|
||||
"modifiedAttributeID": 30,
|
||||
"modifyingAttributeID": 2064,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3348
|
||||
},
|
||||
{
|
||||
"domain": "shipID",
|
||||
"func": "LocationRequiredSkillModifier",
|
||||
"modifiedAttributeID": 50,
|
||||
"modifyingAttributeID": 2064,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3348
|
||||
}
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeChance": 0
|
||||
},
|
||||
"12790": {
|
||||
"disallowAutoRepeat": 0,
|
||||
"effectCategory": 0,
|
||||
"effectID": 12790,
|
||||
"effectName": "shipBonusTorpedoAndCruiseMissileExplosionVelocityCBC2",
|
||||
"electronicChance": 0,
|
||||
"isAssistance": 0,
|
||||
"isOffensive": 0,
|
||||
"isWarpSafe": 0,
|
||||
"modifierInfo": [
|
||||
{
|
||||
"domain": "charID",
|
||||
"func": "OwnerRequiredSkillModifier",
|
||||
"modifiedAttributeID": 653,
|
||||
"modifyingAttributeID": 745,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3325
|
||||
},
|
||||
{
|
||||
"domain": "charID",
|
||||
"func": "OwnerRequiredSkillModifier",
|
||||
"modifiedAttributeID": 653,
|
||||
"modifyingAttributeID": 745,
|
||||
"operation": 6,
|
||||
"skillTypeID": 3326
|
||||
}
|
||||
],
|
||||
"propulsionChance": 0,
|
||||
"published": 0,
|
||||
"rangeAttributeID": 54,
|
||||
"rangeChance": 0
|
||||
}
|
||||
}
|
||||
@@ -30372,15 +30372,15 @@
|
||||
"categoryID": 11,
|
||||
"fittableNonSingleton": 0,
|
||||
"groupID": 4949,
|
||||
"groupName_de": "Interbus-Lieferdrohnen",
|
||||
"groupName_de": "Interbus-Yoiul-LADs",
|
||||
"groupName_en-us": "InterBus Yoiul LADs",
|
||||
"groupName_es": "Drones de entrega de InterBus",
|
||||
"groupName_fr": "Drones de livraison InterBus",
|
||||
"groupName_es": "MAL de Yoiul de InterBus",
|
||||
"groupName_fr": "LUTINs de Yoiul d'InterBus",
|
||||
"groupName_it": "InterBus Yoiul LADs",
|
||||
"groupName_ja": "インターバス配送ドローン",
|
||||
"groupName_ko": "인터버스 배송 드론",
|
||||
"groupName_ru": "Дроны-доставщики «ИнтерБас»",
|
||||
"groupName_zh": "星际捷运配送无人机",
|
||||
"groupName_ja": "インターバス・ヨイウルLAD",
|
||||
"groupName_ko": "인터버스 요이얼 LAD",
|
||||
"groupName_ru": "Йольские ГАДы консорциума «ИнтерБас»",
|
||||
"groupName_zh": "星际捷运尤尔节自动化物流配送舰",
|
||||
"groupNameID": 1025585,
|
||||
"published": 0,
|
||||
"useBasePrice": 0
|
||||
|
||||
@@ -30076,6 +30076,12 @@
|
||||
"3436": 1,
|
||||
"9955": 5
|
||||
},
|
||||
"89807": {
|
||||
"33096": 1
|
||||
},
|
||||
"89808": {
|
||||
"35680": 1
|
||||
},
|
||||
"90037": {
|
||||
"11584": 3
|
||||
},
|
||||
@@ -30249,9 +30255,6 @@
|
||||
"43703": 1,
|
||||
"60515": 1
|
||||
},
|
||||
"90665": {
|
||||
"3386": 1
|
||||
},
|
||||
"90669": {
|
||||
"3405": 1
|
||||
},
|
||||
|
||||
@@ -886553,6 +886553,10 @@
|
||||
"attributeID": 1299,
|
||||
"value": 1534.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1303,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1544,
|
||||
"value": 1.0
|
||||
@@ -958030,6 +958034,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1302,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
@@ -958200,6 +958208,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1302,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
@@ -958540,6 +958552,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1302,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
@@ -958710,6 +958726,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1302,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
@@ -970515,6 +970535,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1302,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
@@ -970697,6 +970721,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1302,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
@@ -970879,6 +970907,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1303,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
@@ -971061,6 +971093,10 @@
|
||||
"attributeID": 1301,
|
||||
"value": 540.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1302,
|
||||
"value": 89808.0
|
||||
},
|
||||
{
|
||||
"attributeID": 1795,
|
||||
"value": 60000.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -172397,10 +172397,10 @@
|
||||
"basePrice": 0.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Der prachtvolle Pokal des Allianzturniers wandert nach jedem Turnier an den neuen Sieger und ist vielleicht der begehrteste Preis in New Eden. Ruhm und Ehre erwarten die Besitzer, welche ihre Gegner – Elite-Kampfteams – in einem der zermürbendsten, blutigsten und aufregendsten Turniere aller Zeiten besiegen mussten. <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Jahr 107 – Gewinner des 1. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Jahr 107 – Gewinner des 2. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Jahr 108 – Gewinner des 3. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> Jahr 109 – Gewinner des 4. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> Jahr 110 – Gewinner des 5. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Jahr 111 – Gewinner des 6. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Jahr 111 – Gewinner des 7. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Jahr 112 – Gewinner des 8. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Jahr 113 – Gewinner des 9. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> Jahr 114 – Gewinner des 10. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Jahr 115 – Gewinner des 11. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> Jahr 116 – Gewinner des 12. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Jahr 117 – Gewinner des 13. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Jahr 118 – Gewinner des 14. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Jahr 119 – Gewinner des 15. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Jahr 120 – Gewinner des 16. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Jahr 123 – Gewinner des 17. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> Jahr 124 – Gewinner des 18. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> Jahr 125 – Gewinner des 19. Allianzturniers</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Jahr 126 – Gewinner des 20. Allianzturniers</div>",
|
||||
"description_en-us": "Passed on to the winners after every tournament, the magnificent Alliance Tournament cup is perhaps the most coveted prize in New Eden.\r\n\r\nGlory and fame await the holders, who had to defeat the competition posed by the elite combat teams of their opponents in the one of the most gruelling, bloody and exciting tournaments in existence. \r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 1st Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 2nd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 108 - 3rd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> Year 109 - 4th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> Year 110 - 5th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 6th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 7th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 112 - 8th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 113 - 9th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> Year 114 - 10th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 115 - 11th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> Year 116 - 12th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 117 - 13th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 118 - 14th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 119 - 15th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 120 - 16th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 123 - 17th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> Year 124 - 18th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> Year 125 - 19th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 126 - 20th Alliance Tournament Winners</div>",
|
||||
"description_en-us": "Passed on to the winners after every tournament, the magnificent Alliance Tournament cup is perhaps the most coveted prize in New Eden.\r\n\r\nGlory and fame await the holders, who had to defeat the competition posed by the elite combat teams of their opponents in the one of the most gruelling, bloody and exciting tournaments in existence. \r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 1st Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 2nd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 108 - 3rd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> Year 109 - 4th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> Year 110 - 5th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 6th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 7th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 112 - 8th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 113 - 9th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> Year 114 - 10th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 115 - 11th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> Year 116 - 12th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 117 - 13th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 118 - 14th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 119 - 15th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 120 - 16th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 123 - 17th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> Year 124 - 18th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> Year 125 - 19th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 126 - 20th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 127 - 21st Alliance Tournament Winners</div>",
|
||||
"description_es": "Entregada a los ganadores después de cada torneo, la magnífica copa del Torneo de Alianzas es quizás el premio más codiciado de Nuevo Edén.\r\n\r\nLa gloria y la fama aguardan a los ganadores que tuvieron que derrotar a los equipos de combate de élite de sus oponentes en uno de los torneos más agotadores, sangrientos y emocionantes que existen. \r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Año 107: Ganadores del I Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Año 107: Ganadores del II Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Año 108: Ganadores del III Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> Año 109: Ganadores del IV Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> Año 110: Ganadores del V Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Año 111: Ganadores del VI Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Año 111: Ganadores del VII Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Año 112: Ganadores del VIII Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Año 113: Ganadores del IX Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> Año 114: Ganadores del X Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Año 115: Ganadores del XI Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> Año 116: Ganadores del XII Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Año 117: Ganadores del XIII Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Año 118: Ganadores del XIV Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Año 119: Ganadores del XV Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Año 120: Ganadores del XVI Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Año 123: Ganadores del XVII Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> Año 124: Ganadores del XVIII Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity</url> Año 125: Ganadores del XIX Torneo de Alianzas</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Año 126: Ganadores del XX Torneo de Alianzas</div>",
|
||||
"description_fr": "Convoitée entre toutes, cette splendide coupe est remise aux vainqueurs du Tournoi des alliances de New Eden. Pour mériter ce symbole de gloire et de notoriété, les concurrents doivent triompher des équipes de combattants d'élite adverses dans l'un des tournois les plus sanglants et les plus passionnants qui soient. <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Année 107 - Vainqueurs du 1er Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Année 107 - Vainqueurs du 2e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Année 108 - Vainqueurs du 3e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> Année 109 - Vainqueurs du 4e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> Année 110 - Vainqueurs du 5e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Année 111 - Vainqueurs du 6e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Année 111 - Vainqueurs du 7e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Année 112 - Vainqueurs du 8e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Année 113 - Vainqueurs du 9e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> Année 114 - Vainqueurs du 10e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Année 115 - Vainqueurs du 11e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> Année 116 - Vainqueurs du 12e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Année 117 - Vainqueurs du 13e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Année 118 - Vainqueurs du 14e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Année 119 - Vainqueurs du 15e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Année 120 - Vainqueurs du 16e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Année 123 - Vainqueurs du 17e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> Année 124 - Vainqueurs du 18e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> Année 125 - Vainqueurs du 19e Alliance Tournament</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Année 126 - Vainqueurs du 20e Alliance Tournament</div>",
|
||||
"description_it": "Passed on to the winners after every tournament, the magnificent Alliance Tournament cup is perhaps the most coveted prize in New Eden.\r\n\r\nGlory and fame await the holders, who had to defeat the competition posed by the elite combat teams of their opponents in the one of the most gruelling, bloody and exciting tournaments in existence. \r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 1st Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 2nd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 108 - 3rd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> Year 109 - 4th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> Year 110 - 5th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 6th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 7th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 112 - 8th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 113 - 9th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> Year 114 - 10th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 115 - 11th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> Year 116 - 12th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 117 - 13th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 118 - 14th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 119 - 15th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 120 - 16th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 123 - 17th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> Year 124 - 18th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> Year 125 - 19th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 126 - 20th Alliance Tournament Winners</div>",
|
||||
"description_it": "Passed on to the winners after every tournament, the magnificent Alliance Tournament cup is perhaps the most coveted prize in New Eden.\r\n\r\nGlory and fame await the holders, who had to defeat the competition posed by the elite combat teams of their opponents in the one of the most gruelling, bloody and exciting tournaments in existence. \r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 1st Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 107 - 2nd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> Year 108 - 3rd Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> Year 109 - 4th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> Year 110 - 5th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 6th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 111 - 7th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 112 - 8th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 113 - 9th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> Year 114 - 10th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 115 - 11th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> Year 116 - 12th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> Year 117 - 13th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 118 - 14th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 119 - 15th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> Year 120 - 16th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> Year 123 - 17th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> Year 124 - 18th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> Year 125 - 19th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 126 - 20th Alliance Tournament Winners</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> Year 127 - 21st Alliance Tournament Winners</div>",
|
||||
"description_ja": "トーナメントの優勝者に引き継がれる、権威あるアライアンス優勝杯。ニューエデンで多くの人々が手にしたいと願っている最高の褒章かもしれない。\r\n\r\n手にした者には栄光と名声が待っているが、これを手に入れるためには極めて過酷で残忍で激しいこのトーナメントで、立ちはだかるエリートコンバットチームの数々に勝利しなければならない。 \r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url>YC 107年 - 第1回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url>YC 107年 - 第2回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url>YC 108年 - 第3回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url>YC 109年 - 第4回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url>YC 110年 - 第5回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url>YC 111年 - 第6回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url>YC 111年 - 第7回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url>YC 112年 - 第8回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url>YC 113年 - 第9回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url>YC 114年 - 第10回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url>YC 115年 - 第11回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url>YC 116年 - 第12回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url>YC 117年 - 第13回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url>YC 118年 - 第14回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url>YC 119年 - 第15回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url>YC 120年 - 第16回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url>YC 123年 - 第17回アライアンストーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOUR. LIGHT.</url>YC 124年 - 第18回アライアンス・トーナメントの勝者</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity</url>YC 125年 - 第19回アライアンス・トーナメント優勝</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url>YC 126年 - 第20回アライアンストーナメント優勝</div>",
|
||||
"description_ko": "매 토너먼트마다 승자에게 쥐어지는 얼라이언스 토너먼트 우승컵은 뉴에덴의 파일럿들이 가장 탐내는 상품일 것입니다.\r\n\r\n역사상 가장 가혹하면서도 흥분되는 이 토너먼트에서 벌어지는 치열한 경쟁에서 승리한 팀만이 이 우승컵을 쥘 수 있는 명예와 영광을 누릴 수 있습니다.\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> 107년 - 제1회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> 107년 - 제2회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> 108년 - 제3회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> 109년 - 제4회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> 110년 - 제5회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 111년 - 제6회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 111년 - 제7회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 112년 - 제8회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> 113년 - 제9회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> 114년 - 제10회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 115년 - 제11회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> 116년 - 제12회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 117년 - 제13회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> 118년 - 제14회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> 119년 - 제15회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> 120년 - 제16회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> 123년 - 제17회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> 124년 - 제18회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> 125년 - 제19회 얼라이언스 토너먼트 우승팀</div>\r\n\r\n<div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> 126년 - 제20회 얼라이언스 토너먼트 우승팀</div>",
|
||||
"description_ru": "Кубок Турнира альянсов достаётся победителю последнего турнира. Из всех наград в Новом Эдеме эта — самая желанная. Обладателей кубка ждёт заслуженная слава, ведь для победы в этом жестоком, но увлекательном турнире нужно одолеть элитные боевые отряды весьма грозных противников. <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> 107 г. — победители 1-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> 107 г. — победители 2-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//632866070>Band of Brothers</url> 108 г. — победители 3-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//1399057309>HUN Reloaded</url> 109 г. — победители 4-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//1438160193>Ev0ke</url> 110 г. — победители 5-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 111 г. — победители 6-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 111 г. — победители 7-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 112 г. — победители 8-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> 113 г. — победители 9-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99001968>Verge of Collapse</url> 114 г. — победители 10-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 115 г. — победители 11-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004300>The Camel Empire</url> 116 г. — победители 12-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//386292982>Pandemic Legion</url> 117 г. — победители 13-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> 118 г. — победители 14-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> 119 г. — победители 15-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99006468>VYDRA RELOLDED</url> 120 г. — победители 16-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//551692893>HYDRA RELOADED</url> 123 г. — победители 17-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99011706>TRUTH. HONOR. LIGHT.</url> 124 г. — победители 18-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99003581>Fraternity.</url> 125 г. — победители 19-го Турнира альянсов</div> <div style=\"text-align:center;\"><url=showinfo:16159//99004357>The Tuskers Co.</url> 126 г. — победители 20-го Турнира альянсов</div>",
|
||||
|
||||
@@ -178117,6 +178117,7 @@
|
||||
"descriptionID": 522185,
|
||||
"groupID": 1770,
|
||||
"iconID": 21687,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 1,
|
||||
@@ -178153,6 +178154,7 @@
|
||||
"descriptionID": 522191,
|
||||
"groupID": 1770,
|
||||
"iconID": 21691,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 1,
|
||||
@@ -178225,6 +178227,7 @@
|
||||
"descriptionID": 522212,
|
||||
"groupID": 1770,
|
||||
"iconID": 21700,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 1,
|
||||
@@ -178261,6 +178264,7 @@
|
||||
"descriptionID": 522216,
|
||||
"groupID": 1770,
|
||||
"iconID": 21704,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 1,
|
||||
@@ -199677,6 +199681,7 @@
|
||||
"descriptionID": 522187,
|
||||
"groupID": 1770,
|
||||
"iconID": 21687,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 2,
|
||||
@@ -199714,6 +199719,7 @@
|
||||
"descriptionID": 522194,
|
||||
"groupID": 1770,
|
||||
"iconID": 21691,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 2,
|
||||
@@ -199751,6 +199757,7 @@
|
||||
"descriptionID": 522214,
|
||||
"groupID": 1770,
|
||||
"iconID": 21700,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 2,
|
||||
@@ -199788,6 +199795,7 @@
|
||||
"descriptionID": 522226,
|
||||
"groupID": 1770,
|
||||
"iconID": 21704,
|
||||
"isDynamicType": 0,
|
||||
"marketGroupID": 1633,
|
||||
"mass": 0.0,
|
||||
"metaGroupID": 2,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,407 @@
|
||||
{
|
||||
"364790": {
|
||||
"basePrice": 10770.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Fernsprengsätze der F/41-Reihe gehören zu den stärksten manuell gezündeten Sprengsätzen in New Eden. Jede Einheit ist zuverlässig und effektiv und verwendet eine Mischung aus drei Sprengstoffen, um Mehrfachpanzerungen zu durchschlagen, befestigte Gebäude zu zerstören und Infanterie zu vernichten.\n\nDiese Sprengsätze werden manuell platziert und über eine verschlüsselte Frequenz gezündet, die vom Holographischen Kortex-Interface generiert wird, das eine Datenbank mit einzigartigen Aktivierungscodes für jede platzierte Ladung unterhält. Die Produktreihe F/41 verfügt zusätzlich über weitere fortschrittliche Features wie gehärtete EM-Schaltkreise, einen verschlüsselten Multifrequenzempfänger und einen leichten Hybridkeramikrahmen.",
|
||||
"description_en-us": "The F/41 series of remote explosives are among the most powerful manually triggered demolitions devices available in New Eden. Each unit is reliable and effective, using a mix of three volatile materials to produce a yield high enough to penetrate layered armor, shatter reinforced structures, and decimate infantry units.\n\nThese explosives are deployed by hand and detonated using a coded frequency generated by the Cortex Holographic Interface, which maintains a database of unique activation ciphers for every charge placed. The F/41 product line also boasts several other advanced features, such as EM hardened circuits, an encrypted multi-frequency receiver, and a lightweight hybrid ceramic frame.",
|
||||
"description_es": "Los explosivos remotos de la serie F/41 son unos de los dispositivos de demolición por activación manual más potentes que existen en Nuevo Edén. Cada unidad es eficaz, segura y emplea una mezcla de tres materiales volátiles tanto para mejorar el rendimiento como para penetrar el blindaje, hecho de capas, destrozar las estructuras reforzadas y diezmar a las unidades de infantería.\n\nEstos explosivos se despliegan manualmente y se detonan mediante una frecuencia codificada, generada por la interfaz holográfica del córtex, que mantiene una base de datos con los códigos de activación individuales de cada carga colocada. La línea de productos F/41 también posee otras características avanzadas, como circuitos electromagnéticos endurecidos, un receptor multifrecuencia encriptado y un armazón cerámico híbrido ligero.",
|
||||
"description_fr": "La série F/41 d'explosifs télécommandés fait partie des engins explosifs à déclenchement manuel parmi les plus puissants qui soient disponibles sur New Eden. Fiable et efficace, chaque unité utilise un mélange de trois matériaux instables afin de produire une explosion assez puissante pour pénétrer un blindage à plusieurs épaisseurs, démolir des structures renforcées et décimer des unités d'infanterie.\n\nCes explosifs sont déployés manuellement et détonnés à l'aide d'une fréquence codée générée par l'interface holographique Cortex, qui maintient une base de données des chiffres d'activation uniques pour chaque charge placée. La ligne de produits F/41 propose également d'autres caractéristiques avancées, telles que des circuits EM renforcés, un récepteur multifréquences encrypté et un châssis hybride léger en céramique.",
|
||||
"description_it": "Gli esplosivi a controllo remoto della serie F/41 sono tra i dispositivi di distruzione manuale più potenti disponibili in New Eden. Ciascuna unità è affidabile ed efficace e sfrutta una combinazione di tre materiali volatili in grado di generare una potenza sufficiente a perforare armature rivestite, demolire strutture rinforzate e decimare unità di fanteria.\n\nQuesti esplosivi vengono lanciati manualmente e fatti esplodere usando una frequenza cifrata generata dall'interfaccia olografica della corteccia, la quale conserva un database di cifre di attivazione singole per ciascuna carica piazzata. Inoltre, la linea di prodotti F/41 offre altre soluzioni avanzate quali i circuiti EM rinforzati, un ricevitore multifrequenza criptato e un telaio in ceramica ibrida leggera.",
|
||||
"description_ja": "リモート爆弾F/41シリーズは、ニューエデンで利用可能な最も強力な手動操作できる破壊装置の一つである。各ユニットは、3つの揮発性物質の混合物を使用して幾重にも重なる装甲を貫通し、強化構造物をも粉砕するに足る力を生み出し、確実に歩兵ユニットを全滅させる。\n\nこれらの爆弾は手動で配置され、コルテックスホログラフィックインターフェースによって生成されたコード化済み周波数を使用して爆発させる。このインターフェースは、すべての装薬のためにユニークな活性化球体のデータベースを保持したものである。またF/41製品ラインは、EMハードナー回路、暗号化された多周波受信機、軽量ハイブリッドセラミックフレームと他のいくつかの高度な機能を誇っている。",
|
||||
"description_ko": "F/41 시리즈의 원격 폭발물은 뉴에덴에서 구할 수 있는 수동 점화 폭발물 중 가장 강력합니다. 폭발성 물질의 혼합으로 안정성 및 화력이 뛰어나 중첩 장갑, 강화 구조물, 그리고 보병을 대상으로 막대한 양의 피해를 입힙니다. <br><br>사용자가 손으로 직접 전개해야 하는 이 폭발물은 코르텍스 홀로그래픽 인터페이스가 생성하는 암호화된 주파수를 통해 점화됩니다. 개별로 전개된 폭발물은 각각의 특수한 활성화 데이터베이스 코드가 존재합니다. F/41 기종은 첨단 기술 도입을 통해 EM 강화 회로, 암호화된 다중 주파수 수신기, 경량 하이브리드 세라믹 구조와 같은 기능을 적극 탑재하였습니다.",
|
||||
"description_ru": "Серия радиоуправляемых взрывных устройств F/41 относится к наиболее разрушительным неавтоматическим орудиям уничтожения Нового Эдема. Каждый из компонентов устройства отличается как надежностью, так и высоким взрывным потенциалом, а их сочетание вызывает взрыв, способный пробить многослойную броню, расколоть армированные структуры и уничтожить пехоту.\n\nЭти взрывные устройства устанавливаются вручную, а детонация производится путем передачи сигнала на закодированной частоте, генерируемой кортексным голографическим интерфейсом, который сохраняет в своей базе данных уникальные активационные коды для каждого из размещенных зарядов. В устройствах серии F/41 имеется еще ряд высокотехнологичных элементов, таких как укрепленные электромагнитные контуры, многочастотный ресивер с системой шифрования и облегченный гибридокерамический каркас.",
|
||||
"description_zh": "F/41系列远距爆炸物是新伊甸中威力最大的手动触发式爆炸装置。每个爆炸装置性能稳定且威力巨大,混合三种挥发性材料,其威力足以穿透多层装甲、击碎强化结构并消灭步兵部队。这种炸药需要手动部署,并通过皮层全息接口生成的编码频率引爆,确保每次安放炸药时都有唯一的激活密码数据库。此外,F/41产品线还有一些其他的高级功能,比如电磁强化电路、加密多频接收器和轻型混合陶瓷结构。",
|
||||
"descriptionID": 287829,
|
||||
"groupID": 351844,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364790,
|
||||
"typeName_de": "F/45 Fernsprengsatz 'Scrapflake'",
|
||||
"typeName_en-us": "'Scrapflake' F/45 Remote Explosive",
|
||||
"typeName_es": "Explosivo remoto F/45 \"Scrapflake\"",
|
||||
"typeName_fr": "Explosif télécommandé F/45 'Grenaille'",
|
||||
"typeName_it": "Esplosivo a controllo remoto F/45 \"Scrapflake\"",
|
||||
"typeName_ja": "「スクラップフレーク」F/45リモート爆弾",
|
||||
"typeName_ko": "'스크랩플레이크' F/45 원격 폭발물",
|
||||
"typeName_ru": "Радиоуправляемое взрывное устройство F/45 производства 'Scrapflake'",
|
||||
"typeName_zh": "“飞屑”级F/45远距爆炸物",
|
||||
"typeNameID": 287828,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364791": {
|
||||
"basePrice": 28845.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Fernsprengsätze der F/41-Reihe gehören zu den stärksten manuell gezündeten Sprengsätzen in New Eden. Jede Einheit ist zuverlässig und effektiv und verwendet eine Mischung aus drei Sprengstoffen, um Mehrfachpanzerungen zu durchschlagen, befestigte Gebäude zu zerstören und Infanterie zu vernichten.\n\nDiese Sprengsätze werden manuell platziert und über eine verschlüsselte Frequenz gezündet, die vom Holographischen Kortex-Interface generiert wird, das eine Datenbank mit einzigartigen Aktivierungscodes für jede platzierte Ladung unterhält. Die Produktreihe F/41 verfügt zusätzlich über weitere fortschrittliche Features wie gehärtete EM-Schaltkreise, einen verschlüsselten Multifrequenzempfänger und einen leichten Hybridkeramikrahmen.",
|
||||
"description_en-us": "The F/41 series of remote explosives are among the most powerful manually triggered demolitions devices available in New Eden. Each unit is reliable and effective, using a mix of three volatile materials to produce a yield high enough to penetrate layered armor, shatter reinforced structures, and decimate infantry units.\n\nThese explosives are deployed by hand and detonated using a coded frequency generated by the Cortex Holographic Interface, which maintains a database of unique activation ciphers for every charge placed. The F/41 product line also boasts several other advanced features, such as EM hardened circuits, an encrypted multi-frequency receiver, and a lightweight hybrid ceramic frame.",
|
||||
"description_es": "Los explosivos remotos de la serie F/41 son unos de los dispositivos de demolición por activación manual más potentes que existen en Nuevo Edén. Cada unidad es eficaz, segura y emplea una mezcla de tres materiales volátiles tanto para mejorar el rendimiento como para penetrar el blindaje, hecho de capas, destrozar las estructuras reforzadas y diezmar a las unidades de infantería.\n\nEstos explosivos se despliegan manualmente y se detonan mediante una frecuencia codificada, generada por la interfaz holográfica del córtex, que mantiene una base de datos con los códigos de activación individuales de cada carga colocada. La línea de productos F/41 también posee otras características avanzadas, como circuitos electromagnéticos endurecidos, un receptor multifrecuencia encriptado y un armazón cerámico híbrido ligero.",
|
||||
"description_fr": "La série F/41 d'explosifs télécommandés fait partie des engins explosifs à déclenchement manuel parmi les plus puissants qui soient disponibles sur New Eden. Fiable et efficace, chaque unité utilise un mélange de trois matériaux instables afin de produire une explosion assez puissante pour pénétrer un blindage à plusieurs épaisseurs, démolir des structures renforcées et décimer des unités d'infanterie.\n\nCes explosifs sont déployés manuellement et détonnés à l'aide d'une fréquence codée générée par l'interface holographique Cortex, qui maintient une base de données des chiffres d'activation uniques pour chaque charge placée. La ligne de produits F/41 propose également d'autres caractéristiques avancées, telles que des circuits EM renforcés, un récepteur multifréquences encrypté et un châssis hybride léger en céramique.",
|
||||
"description_it": "Gli esplosivi a controllo remoto della serie F/41 sono tra i dispositivi di distruzione manuale più potenti disponibili in New Eden. Ciascuna unità è affidabile ed efficace e sfrutta una combinazione di tre materiali volatili in grado di generare una potenza sufficiente a perforare armature rivestite, demolire strutture rinforzate e decimare unità di fanteria.\n\nQuesti esplosivi vengono lanciati manualmente e fatti esplodere usando una frequenza cifrata generata dall'interfaccia olografica della corteccia, la quale conserva un database di cifre di attivazione singole per ciascuna carica piazzata. Inoltre, la linea di prodotti F/41 offre altre soluzioni avanzate quali i circuiti EM rinforzati, un ricevitore multifrequenza criptato e un telaio in ceramica ibrida leggera.",
|
||||
"description_ja": "リモート爆弾F/41シリーズは、ニューエデンで利用可能な最も強力な手動操作できる破壊装置の一つである。各ユニットは、3つの揮発性物質の混合物を使用して幾重にも重なる装甲を貫通し、強化構造物をも粉砕するに足る力を生み出し、確実に歩兵ユニットを全滅させる。\n\nこれらの爆弾は手動で配置され、コルテックスホログラフィックインターフェースによって生成されたコード化済み周波数を使用して爆発させる。このインターフェースは、すべての装薬のためにユニークな活性化球体のデータベースを保持したものである。またF/41製品ラインは、EMハードナー回路、暗号化された多周波受信機、軽量ハイブリッドセラミックフレームと他のいくつかの高度な機能を誇っている。",
|
||||
"description_ko": "F/41 시리즈의 원격 폭발물은 뉴에덴에서 구할 수 있는 수동 점화 폭발물 중 가장 강력합니다. 폭발성 물질의 혼합으로 안정성 및 화력이 뛰어나 중첩 장갑, 강화 구조물, 그리고 보병을 대상으로 막대한 양의 피해를 입힙니다. <br><br>사용자가 손으로 직접 전개해야 하는 이 폭발물은 코르텍스 홀로그래픽 인터페이스가 생성하는 암호화된 주파수를 통해 점화됩니다. 개별로 전개된 폭발물은 각각의 특수한 활성화 데이터베이스 코드가 존재합니다. F/41 기종은 첨단 기술 도입을 통해 EM 강화 회로, 암호화된 다중 주파수 수신기, 경량 하이브리드 세라믹 구조와 같은 기능을 적극 탑재하였습니다.",
|
||||
"description_ru": "Серия радиоуправляемых взрывных устройств F/41 относится к наиболее разрушительным неавтоматическим орудиям уничтожения Нового Эдема. Каждый из компонентов устройства отличается как надежностью, так и высоким взрывным потенциалом, а их сочетание вызывает взрыв, способный пробить многослойную броню, расколоть армированные структуры и уничтожить пехоту.\n\nЭти взрывные устройства устанавливаются вручную, а детонация производится путем передачи сигнала на закодированной частоте, генерируемой кортексным голографическим интерфейсом, который сохраняет в своей базе данных уникальные активационные коды для каждого из размещенных зарядов. В устройствах серии F/41 имеется еще ряд высокотехнологичных элементов, таких как укрепленные электромагнитные контуры, многочастотный ресивер с системой шифрования и облегченный гибридокерамический каркас.",
|
||||
"description_zh": "F/41系列远距爆炸物是新伊甸中威力最大的手动触发式爆炸装置。每个爆炸装置性能稳定且威力巨大,混合三种挥发性材料,其威力足以穿透多层装甲、击碎强化结构并消灭步兵部队。这种炸药需要手动部署,并通过皮层全息接口生成的编码频率引爆,确保每次安放炸药时都有唯一的激活密码数据库。此外,F/41产品线还有一些其他的高级功能,比如电磁强化电路、加密多频接收器和轻型混合陶瓷结构。",
|
||||
"descriptionID": 287831,
|
||||
"groupID": 351844,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364791,
|
||||
"typeName_de": "Boundless-Fernsprengsatz 'Skinjuice'",
|
||||
"typeName_en-us": "'Skinjuice' Boundless Remote Explosive",
|
||||
"typeName_es": "Explosivo remoto Boundless \"Skinjuice\"",
|
||||
"typeName_fr": "Explosif télécommandé Boundless « Skinjuice »",
|
||||
"typeName_it": "Esplosivo a controllo remoto Boundless \"Skinjuice\"",
|
||||
"typeName_ja": "「スキンジュース」バウンドレスリモート爆弾",
|
||||
"typeName_ko": "'스킨쥬스' 바운들리스 원격 폭발물",
|
||||
"typeName_ru": "Радиоуправляемое взрывное устройство 'Skinjuice' производства 'Boundless'",
|
||||
"typeName_zh": "“炼肤”级无限远距爆炸物",
|
||||
"typeNameID": 287830,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364810": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat. HINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune personnalisation spécifique. REMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura di base cablata con tutti gli accessori e i protocolli da combattimento minimi, ma senza personalizzazioni specifiche per il ruolo. NOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘スーツとプロトコルが組み込まれているが、特定任務のカスタマイズはされていない。注:この基本フレームは特定任務ボーナスは受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек. ПРИМЕЧАНИЕ: Данная базовая структура не обладает какими-либо бонусами, обусловленными функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 294206,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364810,
|
||||
"typeName_de": "Leichter Amarr-Rahmen A-I",
|
||||
"typeName_en-us": "Amarr Light Frame A-I",
|
||||
"typeName_es": "Modelo ligero Amarr A-I",
|
||||
"typeName_fr": "Modèle de combinaison légère Amarr A-I",
|
||||
"typeName_it": "Armatura leggera Amarr A-I",
|
||||
"typeName_ja": "アマーライトフレームA-I",
|
||||
"typeName_ko": "아마르 라이트 기본 슈트 A-I",
|
||||
"typeName_ru": "Легкая структура Амарр A-I",
|
||||
"typeName_zh": "艾玛轻型结构A-I",
|
||||
"typeNameID": 294205,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364811": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat. HINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune personnalisation spécifique. REMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura di base cablata con tutti gli accessori e i protocolli da combattimento minimi, ma senza personalizzazioni specifiche per il ruolo. NOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘スーツとプロトコルが組み込まれているが、特定任務のカスタマイズはされていない。注:この基本フレームは特定任務ボーナスは受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек. ПРИМЕЧАНИЕ: Данная базовая структура не обладает какими-либо бонусами, обусловленными функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 294214,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364811,
|
||||
"typeName_de": "Leichter Caldari-Rahmen C-I",
|
||||
"typeName_en-us": "Caldari Light Frame C-I",
|
||||
"typeName_es": "Modelo ligero Caldari C-I",
|
||||
"typeName_fr": "Modèle de combinaison légère Caldari C-I",
|
||||
"typeName_it": "Armatura leggera Caldari C-I",
|
||||
"typeName_ja": "カルダリライトフレームC-I",
|
||||
"typeName_ko": "칼다리 라이트 기본 슈트 C-I",
|
||||
"typeName_ru": "Легкая структура Калдари C-I",
|
||||
"typeName_zh": "加达里轻型结构C-I",
|
||||
"typeNameID": 294213,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364812": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat.\n\nHINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique.\n\nREMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura semplice cablata con tutti gli accessori e i protocolli da combattimento minimi ma senza personalizzazioni specifiche per il ruolo.\n\nNOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘プロトコルが組み込まれているが、特定任務のカスタマイズはされていない。\n\n注:この基本フレームは特定任務ボーナスを受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек.\n\nПРИМЕЧАНИЕ: Данная базовая структура не имеет каких-либо бонусов, обусловленных функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 287871,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364812,
|
||||
"typeName_de": "Leichter Gallente-Rahmen G-I",
|
||||
"typeName_en-us": "Gallente Light Frame G-I",
|
||||
"typeName_es": "Modelo ligero Gallente G-I",
|
||||
"typeName_fr": "Modèle de combinaison Légère Gallente G-I",
|
||||
"typeName_it": "Armatura leggera Gallente G-I",
|
||||
"typeName_ja": "ガレンテライトフレームG-I",
|
||||
"typeName_ko": "갈란테 라이트 기본 슈트 G-I",
|
||||
"typeName_ru": "Легкая структура Галленте G-I",
|
||||
"typeName_zh": "盖伦特轻型结构G-I",
|
||||
"typeNameID": 287870,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364813": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat.\n\nHINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un modelo de traje de salto básico con todo el equipamiento y los protocolos de combate mínimos, pero sin ningún ajuste asociado a un rol de combate específico.\n\nAVISO: este modelo básico no obtiene bonificaciones de ningún rol de combate.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique.\n\nREMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura semplice cablata con tutti gli accessori e i protocolli da combattimento minimi ma senza personalizzazioni specifiche per il ruolo.\n\nNOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘プロトコルが組み込まれているが、特定任務のカスタマイズはされていない。\n\n注:この基本フレームは特定任務ボーナスを受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек.\n\nПРИМЕЧАНИЕ: Данная базовая структура не имеет каких-либо бонусов, обусловленных функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 287877,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364813,
|
||||
"typeName_de": "Leichter Minmatar-Rahmen M-I",
|
||||
"typeName_en-us": "Minmatar Light Frame M-I",
|
||||
"typeName_es": "Modelo ligero Minmatar M-I",
|
||||
"typeName_fr": "Modèle de combinaison Légère Minmatar M-I",
|
||||
"typeName_it": "Armatura leggera Minmatar M-I",
|
||||
"typeName_ja": "ミンマターライトフレームM-I",
|
||||
"typeName_ko": "민마타 라이트 기본 슈트 M-I",
|
||||
"typeName_ru": "Легкая структура Минматар M-I",
|
||||
"typeName_zh": "米玛塔尔轻型结构M-I",
|
||||
"typeNameID": 287876,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364814": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat.\n\nHINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique.\n\nREMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura semplice cablata con tutti gli accessori e i protocolli da combattimento minimi ma senza personalizzazioni specifiche per il ruolo.\n\nNOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘プロトコルが組み込まれているが、特定任務のカスタマイズはされていない。\n\n注:この基本フレームは特定任務ボーナスを受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек.\n\nПРИМЕЧАНИЕ: Данная базовая структура не имеет каких-либо бонусов, обусловленных функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 287901,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364814,
|
||||
"typeName_de": "Mittlerer Amarr-Rahmen A-I",
|
||||
"typeName_en-us": "Amarr Medium Frame A-I",
|
||||
"typeName_es": "Modelo medio Amarr A-I",
|
||||
"typeName_fr": "Modèle de combinaison Moyenne Amarr A-I",
|
||||
"typeName_it": "Armatura media Amarr A-I",
|
||||
"typeName_ja": "アマーミディアムフレームA-I",
|
||||
"typeName_ko": "아마르 중형 기본 슈트 A-I",
|
||||
"typeName_ru": "Средняя структура Амарр A-I",
|
||||
"typeName_zh": "艾玛中型结构A-I",
|
||||
"typeNameID": 287900,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364815": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat.\n\nHINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique.\n\nREMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura semplice cablata con tutti gli accessori e i protocolli da combattimento minimi ma senza personalizzazioni specifiche per il ruolo.\n\nNOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘プロトコルが組み込まれているが、特定任務のカスタマイズはされていない。\n\n注:この基本フレームは特定任務ボーナスを受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек.\n\nПРИМЕЧАНИЕ: Данная базовая структура не имеет каких-либо бонусов, обусловленных функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 287895,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364815,
|
||||
"typeName_de": "Mittlerer Caldari-Rahmen C-I",
|
||||
"typeName_en-us": "Caldari Medium Frame C-I",
|
||||
"typeName_es": "Modelo medio Caldari C-I",
|
||||
"typeName_fr": "Modèle de combinaison Moyenne Caldari C-I",
|
||||
"typeName_it": "Armatura media Caldari C-I",
|
||||
"typeName_ja": "カルダリミディアムフレームC-I",
|
||||
"typeName_ko": "칼다리 중형 기본 슈트C-I",
|
||||
"typeName_ru": "Средняя структура Калдари C-I",
|
||||
"typeName_zh": "加达里中型结构C-I",
|
||||
"typeNameID": 287894,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364816": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat.\n\nHINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique.\n\nREMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura semplice cablata con tutti gli accessori e i protocolli da combattimento minimi ma senza personalizzazioni specifiche per il ruolo.\n\nNOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘プロトコルが組み込まれているが、特定任務のカスタマイズはされていない。\n\n注:この基本フレームは特定任務ボーナスを受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек.\n\nПРИМЕЧАНИЕ: Данная базовая структура не имеет каких-либо бонусов, обусловленных функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 287889,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364816,
|
||||
"typeName_de": "Mittlerer Gallente-Rahmen G-I",
|
||||
"typeName_en-us": "Gallente Medium Frame G-I",
|
||||
"typeName_es": "Modelo medio Gallente G-I",
|
||||
"typeName_fr": "Modèle de combinaison Moyenne Gallente G-I",
|
||||
"typeName_it": "Armatura media Gallente G-I",
|
||||
"typeName_ja": "ガレンテミディアムフレームG-I",
|
||||
"typeName_ko": "갈란테 중형 기본 슈트G-I",
|
||||
"typeName_ru": "Средняя структура Галленте G-I",
|
||||
"typeName_zh": "盖伦特中型结构G-I",
|
||||
"typeNameID": 287888,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364817": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat.\n\nHINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique.\n\nREMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura semplice cablata con tutti gli accessori e i protocolli da combattimento minimi ma senza personalizzazioni specifiche per il ruolo.\n\nNOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘プロトコルが組み込まれているが、特定任務のカスタマイズはされていない。\n\n注:この基本フレームは特定任務ボーナスを受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек.\n\nПРИМЕЧАНИЕ: Данная базовая структура не имеет каких-либо бонусов, обусловленных функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 287883,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364817,
|
||||
"typeName_de": "Mittlerer Minmatar-Rahmen M-I",
|
||||
"typeName_en-us": "Minmatar Medium Frame M-I",
|
||||
"typeName_es": "Modelo medio Minmatar M-I",
|
||||
"typeName_fr": "Modèle de combinaison Moyenne Minmatar M-I",
|
||||
"typeName_it": "Armatura media Minmatar M-I",
|
||||
"typeName_ja": "ミンマターミディアムフレームM-I",
|
||||
"typeName_ko": "민마타 중형 기본 슈트M-I",
|
||||
"typeName_ru": "Средняя структура Минматар M-I",
|
||||
"typeName_zh": "米玛塔尔中型结构M-I",
|
||||
"typeNameID": 287882,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364818": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat.\n\nHINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique.\n\nREMARQUE : Ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura semplice cablata con tutti gli accessori e i protocolli da combattimento minimi ma senza personalizzazioni specifiche per il ruolo.\n\nNOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘プロトコルが組み込まれているが、特定任務のカスタマイズはされていない。\n\n注:この基本フレームは特定任務ボーナスを受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек.\n\nПРИМЕЧАНИЕ: Данная базовая структура не имеет каких-либо бонусов, обусловленных функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 287865,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364818,
|
||||
"typeName_de": "Schwerer A-I Amarr-Rahmen",
|
||||
"typeName_en-us": "Amarr Heavy Frame A-I",
|
||||
"typeName_es": "Modelo pesado Amarr A-I",
|
||||
"typeName_fr": "Modèle de combinaison Lourde Amarr A-I",
|
||||
"typeName_it": "Armatura pesante Amarr A-I",
|
||||
"typeName_ja": "アマーヘビーフレームA-I",
|
||||
"typeName_ko": "아마르 헤비 기본 슈트 A-I",
|
||||
"typeName_ru": "Тяжелая структура Амарр серии G-I",
|
||||
"typeName_zh": "艾玛重型结构A-I",
|
||||
"typeNameID": 287864,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364819": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat. HINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune modification spécifique. REMARQUE : ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura di base cablata con tutti gli accessori e i protocolli da combattimento minimi, ma senza personalizzazioni specifiche per il ruolo. NOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘スーツとプロトコルが組み込まれているが、特定任務のカスタマイズはされていない。注:この基本フレームは特定任務ボーナスは受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек. ПРИМЕЧАНИЕ: Данная базовая структура не обладает какими-либо бонусами, обусловленными функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 294110,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364819,
|
||||
"typeName_de": "Schwerer Caldari-Rahmen C-I",
|
||||
"typeName_en-us": "Caldari Heavy Frame C-I",
|
||||
"typeName_es": "Modelo pesado Caldari C-I",
|
||||
"typeName_fr": "Modèle de combinaison lourde Caldari C-I",
|
||||
"typeName_it": "Armatura pesante Caldari C-I",
|
||||
"typeName_ja": "カルダリヘビーフレームC-I",
|
||||
"typeName_ko": "칼다리 헤비 기본 슈트 C-I",
|
||||
"typeName_ru": "Тяжелая структура Калдари C-I",
|
||||
"typeName_zh": "加达里重型结构C-I",
|
||||
"typeNameID": 294109,
|
||||
"volume": 0.0
|
||||
},
|
||||
"364820": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
"description_de": "Ein einfacher Dropsuitrahmen, der mit allen minimal festgelegten Kampffolgen und Protokollen festverdrahtet ist, aber keine funktionsspezifischen Anpassungen hat. HINWEIS: Dieser einfache Rahmen erhält keine funktionsspezifischen Boni.",
|
||||
"description_en-us": "A basic dropsuit frame hardwired with all minimum designation combat suites and protocols but without any role-specific customizations.\r\n\r\nNOTE: This basic frame does not receive any role-specific bonuses.",
|
||||
"description_es": "Un armazón de traje básico, equipado con los materiales y protocolos mínimos obligatorios de combate. No está customizado para ninguna función en particular.\n\nNOTA: Este armazón básico no recibe ninguna bonificación específica de función.",
|
||||
"description_fr": "Un modèle de combinaison de base disposant de toutes les suites et protocoles de combat de classement minimum, mais sans aucune personnalisation spécifique. REMARQUE : ce modèle de base ne reçoit pas de bonus spécifique d'un rôle particulier.",
|
||||
"description_it": "Un'armatura di base cablata con tutti gli accessori e i protocolli da combattimento minimi, ma senza personalizzazioni specifiche per il ruolo. NOTA: questa armatura di base non riceve alcun bonus specifico per il ruolo.",
|
||||
"description_ja": "基本的な降下スーツフレームで、最低限全ての戦闘スーツとプロトコルが組み込まれているが、特定任務のカスタマイズはされていない。注:この基本フレームは特定任務ボーナスは受け取らない。",
|
||||
"description_ko": "전투용 설계 및 프로토콜이 탑재된 기본형 프레임으로 임무 특화 커스터마이즈는 이루어지지 않았습니다. <br><br>참고: 기본 프레임은 임무 특성 보너스가 존재하지 않습니다.",
|
||||
"description_ru": "Базовая структура скафандра с аппаратной прошивкой простейших вариантов всех боевых скафандров и протоколов, но без каких-либо определяющих функциональное назначение настроек. ПРИМЕЧАНИЕ: Данная базовая структура не обладает какими-либо бонусами, обусловленными функциональным назначением.",
|
||||
"description_zh": "基础款作战服结构,配备所有最低标准的作战模块和协议,但没有针对任何角色进行定制。注:这种基础款结构无法获得任何角色加成。",
|
||||
"descriptionID": 294118,
|
||||
"groupID": 351064,
|
||||
"mass": 0.0,
|
||||
"portionSize": 1,
|
||||
"published": 0,
|
||||
"radius": 1.0,
|
||||
"typeID": 364820,
|
||||
"typeName_de": "Schwerer Gallente-Rahmen G-I",
|
||||
"typeName_en-us": "Gallente Heavy Frame G-I",
|
||||
"typeName_es": "Modelo pesado Gallente G-I",
|
||||
"typeName_fr": "Modèle de combinaison lourde Gallente G-I",
|
||||
"typeName_it": "Armatura pesante Gallente G-I",
|
||||
"typeName_ja": "ガレンテヘビーフレームG-I",
|
||||
"typeName_ko": "갈란테 헤비 기본 슈트 G-I",
|
||||
"typeName_ru": "Тяжелая структура Галленте G-I",
|
||||
"typeName_zh": "盖伦特重型结构G-I",
|
||||
"typeNameID": 294117,
|
||||
"volume": 0.01
|
||||
},
|
||||
"364821": {
|
||||
"basePrice": 3000.0,
|
||||
"capacity": 0.0,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[
|
||||
{
|
||||
"field_name": "client_build",
|
||||
"field_value": 3134917
|
||||
"field_value": 3137986
|
||||
},
|
||||
{
|
||||
"field_name": "dump_time",
|
||||
"field_value": 1765284791
|
||||
"field_value": 1765458681
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
47
tests/test_pyfa_cli_stats.py
Normal file
47
tests/test_pyfa_cli_stats.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(os.path.realpath(os.path.join(script_dir, "..")))
|
||||
|
||||
from scripts import pyfa_cli_stats # noqa: E402
|
||||
|
||||
|
||||
ISHTAR_SPIDER_FIT = """[Ishtar, Spider]
|
||||
|
||||
Capacitor Power Relay II
|
||||
Drone Damage Amplifier II
|
||||
Explosive Armor Hardener II
|
||||
Multispectrum Energized Membrane II
|
||||
Reactive Armor Hardener
|
||||
Shadow Serpentis EM Armor Hardener
|
||||
|
||||
Cap Recharger II
|
||||
Omnidirectional Tracking Link II, Tracking Speed Script
|
||||
Medium Compact Pb-Acid Cap Battery
|
||||
Republic Fleet Large Cap Battery
|
||||
|
||||
Medium Remote Armor Repairer II
|
||||
Medium Remote Armor Repairer II
|
||||
Medium Remote Armor Repairer II
|
||||
Medium Remote Armor Repairer II
|
||||
|
||||
Medium Explosive Armor Reinforcer II
|
||||
Medium Thermal Armor Reinforcer II
|
||||
|
||||
|
||||
Valkyrie II x5
|
||||
Berserker II x5
|
||||
"""
|
||||
|
||||
|
||||
def test_ishtar_spider_remote_armor_reps():
|
||||
payload = {"fit": ISHTAR_SPIDER_FIT}
|
||||
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
|
||||
data = pyfa_cli_stats.compute_stats(payload, tmp)
|
||||
armor_rps = data["remote_reps_outgoing"]["current"]["armor"]
|
||||
assert armor_rps is not None
|
||||
assert int(round(armor_rps)) == 171
|
||||
922
uv.lock
generated
Normal file
922
uv.lock
generated
Normal file
@@ -0,0 +1,922 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "25.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "soupsieve" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/0b/44c39cf3b18a9280950ad63a579ce395dda4c32193ee9da7ff0aed547094/beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da", size = 505113, upload-time = "2023-04-07T15:02:49.038Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/57/f4/a69c20ee4f660081a7dedb1ac57f29be9378e04edfcb90c526b923d4bebc/beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a", size = 142979, upload-time = "2023-04-07T15:02:50.77Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cattrs"
|
||||
version = "25.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/00/2432bb2d445b39b5407f0a90e01b9a271475eea7caf913d7a86bcb956385/cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a", size = 509321, upload-time = "2025-10-07T12:26:08.737Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/2b/a40e1488fdfa02d3f9a653a61a5935ea08b3c2225ee818db6a76c7ba9695/cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff", size = 70738, upload-time = "2025-10-07T12:26:06.603Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.11.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "contourpy"
|
||||
version = "1.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "42.0.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/d8/214d25515bf6034dce99aba22eeb47443b14c82160114e3d3f33067c6d3b/cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb", size = 670311, upload-time = "2024-02-21T03:07:29.752Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/08/87/58a38f8f4d0fe388aaceec8d4b91644cc1edfe4fd3b9ccf5dad414f60738/cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449", size = 5874004, upload-time = "2024-02-21T03:06:15.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/3a/2138a6246804d5d0b588031ad9795c7672317591a9ec2c5962cb65a912c3/cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18", size = 3098630, upload-time = "2024-02-21T03:07:08.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/3d/f4e69dd3773826c12a7eeb7e7550616c0932e7c4cc3c677023964f137b46/cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2", size = 4371589, upload-time = "2024-02-21T03:06:56.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/d1/6ca412147384b0b02cb343a3e8b7d3fde7ac6833c29fee9ca91f59ab5fdf/cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1", size = 4561822, upload-time = "2024-02-21T03:06:41.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/c8/df35ce0519febdf46ebfab1a6ef1278c95a45b060a613fac57de64aa727e/cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b", size = 4355915, upload-time = "2024-02-21T03:06:34.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/ed/e8ad5637b8ac1fd1b48fadb11dcf3649083af5c4298dfdc81d0382de9191/cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1", size = 4573394, upload-time = "2024-02-21T03:07:06.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/98/c4a7fd53fd27619d5baa6102af7833543a8428479b81959942aeb98b278e/cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992", size = 4471747, upload-time = "2024-02-21T03:06:18.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/dc/9beb49116370d8086a10d1dd0b5cbe7816efeebdfda414f3fd09a95b8525/cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885", size = 4646204, upload-time = "2024-02-21T03:06:50.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/a8/200e5afdb96ac3ebd4025dcc18cc43e73b1e4f6d839b7f2c9f1eb1a6a6c6/cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824", size = 4444931, upload-time = "2024-02-21T03:07:13.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/74/3bbe47d186ed91e3fa7ffb4426f39cb63d299842a9c600f4834530599bd4/cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b", size = 4645892, upload-time = "2024-02-21T03:06:37.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/32/6d4907f82d2870959ad3dfc21f0a20b6501c143034d249df852281ae3543/cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925", size = 2430337, upload-time = "2024-02-21T03:06:43.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/70/08b9cc6690e252541286fcfda770023a27aa0e1a1f8a1df52ee052c0494b/cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923", size = 2885772, upload-time = "2024-02-21T03:07:19.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/71/b9ed937252fad47d8d24746b876ca6f2dc31bd495e78f5b77a5082d73ae2/cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7", size = 5873636, upload-time = "2024-02-21T03:06:55.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/61/644e21048102cd72a13325fd6443db741746fbf0157e7c5d5c7628afc336/cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52", size = 4372889, upload-time = "2024-02-21T03:06:25.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/c2/4ff3cf950504aa6ccd3db3712f515151536eea0cf6125442015b0532a46d/cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a", size = 4561544, upload-time = "2024-02-21T03:06:20.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/e1/18056b2c0e4ba031ea6b9d660bc2bdf491f7ef64ab7ef1a803a03a8b8d26/cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9", size = 4357160, upload-time = "2024-02-21T03:07:22.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/45/81f378eb85aab14b229c1032ba3694eff85a3d75b35092c3e71abd2d34f6/cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764", size = 4573002, upload-time = "2024-02-21T03:07:04.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/a1/04733ecbe1e77a228c738f4ab321ca050e45284997f3e3a1539461cd4bca/cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff", size = 4471962, upload-time = "2024-02-21T03:06:12.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/5d/33f17e40dbb7441ad51e8a6920e726f68443cdbfb388cb8eff53e4b6ffd4/cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257", size = 4646395, upload-time = "2024-02-21T03:06:23.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/56/1b2c8aa8e62bfb568022b68d77ebd2bd9afddea37898350fbfe008dcefa7/cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929", size = 4445612, upload-time = "2024-02-21T03:06:58.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/8e/dac70232d4231c53448e29aa4b768cf82d891fcfd6e0caa7ace242da8c9b/cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0", size = 4646026, upload-time = "2024-02-21T03:06:33.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/d7/8b9d29cf3745b928a71b1f6c3c54366272d8d67181d1f056309992d19640/cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129", size = 2430620, upload-time = "2024-02-21T03:07:17.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/df/c220c1be23d1f0ee55f4d1589203936cfa221f95ac5d1b1b342109c1143e/cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854", size = 2887348, upload-time = "2024-02-21T03:06:27.929Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cycler"
|
||||
version = "0.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.61.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logbook"
|
||||
version = "1.7.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/98/c1d93c1d7593f58515333a6217aa4ae647d9ee9c1aa2dfdf77b28b7bb7c7/Logbook-1.7.0.post0.tar.gz", hash = "sha256:a5e8016701ca3beea6a390b0ba1541037f663543ca508ccd36cfdc841639cdd7", size = 367964, upload-time = "2023-11-10T23:33:29.292Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/d0/0d9e4f08333189547ffa85257c3053a07a4c1a431dcbfbe9a49eaedc33dc/Logbook-1.7.0.post0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b1e0b8c3d25a7cf539340c299a8a1fd37d19809a158c8849f6e73a6304f583c7", size = 201359, upload-time = "2023-11-10T23:32:57.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/f3/8c2146de5f2179cb7d8727b8e150def2db3dfa74e0a6341a3921557aff28/Logbook-1.7.0.post0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3f2928505d528fb2efa5c4c7e63bd0a3d5ded4d96db8e056ab61a69313739072", size = 134941, upload-time = "2023-11-10T23:32:58.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/70/26a766173b212b003149b5899cd6616f415a82f11b86a5c5628494cd44cf/Logbook-1.7.0.post0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f891dc3f60a18f85d09698d1ea429b2effb06bfd42a1835493efde5eb6ea08b", size = 130611, upload-time = "2023-11-10T23:33:00.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/d1/0be80be60881e2b5d8747a64558a858c759443ad2d2b7e036272e87cb9f4/Logbook-1.7.0.post0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7d7a39dbfa9c2793569691c719c6f0269a21b01583f33dfcc6052b3312fc5f2", size = 518536, upload-time = "2023-11-10T23:33:02.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/fa/4c717b80b59496010c9fc5d6a372df0215d0c480548baae1c36f443075db/Logbook-1.7.0.post0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40a8e1b4fc0d2fdf8e662fabd8a933ba1ce91a355c755ab7a1d13469ec0e590e", size = 527547, upload-time = "2023-11-10T23:33:04.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/a9/78983454f4b638e9875b783bebce92a634829f3ea50743f9275e4db2a8d3/Logbook-1.7.0.post0-cp312-cp312-win32.whl", hash = "sha256:faf156af8a8954a227f141ffedf7ac1d9ba573b0066f19033ab2240329f94793", size = 115785, upload-time = "2023-11-10T23:33:05.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/34/3bc539cdeffc55e4be527c205b11f7f1f43b665fed8175558dff8c8afa7d/Logbook-1.7.0.post0-cp312-cp312-win_amd64.whl", hash = "sha256:6316fa5eb09b375980a9ee3fd8c72cf9020b532d4c73fd7cbfd7a1a74694739e", size = 122823, upload-time = "2023-11-10T23:33:07.313Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown2"
|
||||
version = "2.4.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/b7/0ba8568968673ba5bcd221525ec364820b46dea9da441146d72cae4df18a/markdown2-2.4.11.tar.gz", hash = "sha256:c04841d0f9df37457396b9d73c54846ddb097a73e7eb7c81d1589e0bba566cda", size = 128610, upload-time = "2023-12-03T18:52:01.29Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/38/183d3c253b9dc884c9d698dd5d68f258d0f853726260362f0fa49400279d/markdown2-2.4.11-py2.py3-none-any.whl", hash = "sha256:2fff5d8e9283218797c5db0e9caad14c4307839919297c5be6ae507d4eeaddbc", size = 41072, upload-time = "2023-12-03T18:51:59.111Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.8.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "contourpy" },
|
||||
{ name = "cycler" },
|
||||
{ name = "fonttools" },
|
||||
{ name = "kiwisolver" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pyparsing" },
|
||||
{ name = "python-dateutil" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/ab/38a0e94cb01dacb50f06957c2bed1c83b8f9dac6618988a37b2487862944/matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1", size = 35866957, upload-time = "2023-11-17T21:16:40.15Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/1b/864d28d5a72d586ac137f4ca54d5afc8b869720e30d508dbd9adcce4d231/matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627", size = 7590988, upload-time = "2023-11-17T21:19:01.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/b0/dd2b60f2dd90fbc21d1d3129c36a453c322d7995d5e3589f5b3c59ee528d/matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4", size = 7483594, upload-time = "2023-11-17T21:19:09.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/da/9942533ad9f96753bde0e5a5d48eacd6c21de8ea1ad16570e31bda8a017f/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d", size = 11380843, upload-time = "2023-11-17T21:19:20.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/52/bfd36eb4745a3b21b3946c2c3a15679b620e14574fe2b98e9451b65ef578/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331", size = 11604608, upload-time = "2023-11-17T21:19:31.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/8c/0cdfbf604d4ea3dfa77435176c51e233cc408ad8f3efbf8d2c9f57cbdafb/matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213", size = 9545252, upload-time = "2023-11-17T21:19:42.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/51/c77a14869b7eb9d6fb440e811b754fc3950d6868c38ace57d0632b674415/matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630", size = 7645067, upload-time = "2023-11-17T21:19:50.091Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.26.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/2b/205ddff2314d4eea852e31d53b8e55eb3f32b292efc3dd86bd827ab9019d/numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea", size = 15664248, upload-time = "2023-11-12T23:17:31.386Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/97/6694e0855b11be0fd8598d484c09edd876ec738a8741025dee072f026c33/numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0", size = 20323012, upload-time = "2023-11-12T23:02:57.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/17/1fdc154e75d24d8c20c42b71bae1b5cf752453f0fc3a2504bbb810293dd1/numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75", size = 13675818, upload-time = "2023-11-12T23:03:32.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/42/a2819c5b77fe6506662ffc13b767e0c216c02f75ae840219013ab822a473/numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7", size = 13917117, upload-time = "2023-11-12T23:03:59.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/89/3b831e2b50c9364069609d1335f46c488a149d5f2be14a08741c92a60009/numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6", size = 17938212, upload-time = "2023-11-12T23:04:32.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/51/f078f1e7f658022150e7c8d5f99d505b40812840349d54667f98bb915b26/numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6", size = 13564269, upload-time = "2023-11-12T23:05:31.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/9f/2f5c6b5f63cf006e6190bf750ade791d1fee353bab654bbde2f83a3ab92e/numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec", size = 17774512, upload-time = "2023-11-12T23:06:03.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/7d/6181c8778cdb15ba0a4959bb72dcc1854c89ca4824481f224c6faf7024e1/numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167", size = 19962368, upload-time = "2023-11-12T23:06:51.561Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/75/3b679b41713bb60e2e8f6e2f87be72c971c9e718b1c17b8f8749240ddca8/numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e", size = 15504951, upload-time = "2023-11-12T23:07:33.828Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714, upload-time = "2023-10-01T13:50:05.279Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011, upload-time = "2023-10-01T13:50:03.745Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.23"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyfa"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "logbook" },
|
||||
{ name = "markdown2" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "python-jose" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "requests-cache" },
|
||||
{ name = "roman" },
|
||||
{ name = "ruff" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "wxpython" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "beautifulsoup4", specifier = "==4.12.2" },
|
||||
{ name = "cryptography", specifier = "==42.0.4" },
|
||||
{ name = "logbook", specifier = "==1.7.0.post0" },
|
||||
{ name = "markdown2", specifier = "==2.4.11" },
|
||||
{ name = "matplotlib", specifier = "==3.8.2" },
|
||||
{ name = "numpy", specifier = "==1.26.2" },
|
||||
{ name = "packaging", specifier = "==23.2" },
|
||||
{ name = "pytest", marker = "extra == 'dev'" },
|
||||
{ name = "python-dateutil", specifier = "==2.8.2" },
|
||||
{ name = "python-jose", specifier = "==3.3.0" },
|
||||
{ name = "pyyaml", specifier = "==6.0.1" },
|
||||
{ name = "requests", specifier = "==2.31.0" },
|
||||
{ name = "requests-cache", specifier = "==1.1.1" },
|
||||
{ name = "roman", specifier = "==4.1" },
|
||||
{ name = "ruff", specifier = ">=0.14.8" },
|
||||
{ name = "sqlalchemy", specifier = "==1.4.50" },
|
||||
{ name = "wxpython", specifier = "==4.2.1" },
|
||||
]
|
||||
provides-extras = ["dev"]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324, upload-time = "2021-07-14T08:19:19.783Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702, upload-time = "2021-07-14T08:19:18.161Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-jose"
|
||||
version = "3.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ecdsa" },
|
||||
{ name = "pyasn1" },
|
||||
{ name = "rsa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/19/b2c86504116dc5f0635d29f802da858404d77d930a25633d2e86a64a35b3/python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a", size = 129068, upload-time = "2021-06-05T03:30:40.895Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/2d/e94b2f7bab6773c70efc70a61d66e312e1febccd9e0db6b9e0adf58cbad1/python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a", size = 33530, upload-time = "2021-06-05T03:30:38.099Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", size = 125201, upload-time = "2023-07-18T00:00:23.308Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/06/1b305bf6aa704343be85444c9d011f626c763abb40c0edc1cad13bfd7f86/PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", size = 178692, upload-time = "2023-08-28T18:43:24.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/02/404de95ced348b73dd84f70e15a41843d817ff8c1744516bf78358f2ffd2/PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", size = 165622, upload-time = "2023-08-28T18:43:26.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/4c/4a2908632fc980da6d918b9de9c1d9d7d7e70b2672b1ad5166ed27841ef7/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", size = 696937, upload-time = "2024-01-18T20:40:22.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/33/720548182ffa8344418126017aa1d4ab4aeec9a2275f04ce3f3573d8ace8/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", size = 724969, upload-time = "2023-08-28T18:43:28.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/78/77b40157b6cb5f2d3d31a3d9b2efd1ba3505371f76730d267e8b32cf4b7f/PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", size = 712604, upload-time = "2023-08-28T18:43:30.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/97/3e0e089ee85e840f4b15bfa00e4e63d84a3691ababbfea92d6f820ea6f21/PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", size = 126098, upload-time = "2023-08-28T18:43:31.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9f/fbade56564ad486809c27b322d0f7e6a89c01f6b4fe208402e90d4443a99/PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", size = 138675, upload-time = "2023-08-28T18:43:33.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.31.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794, upload-time = "2023-05-22T15:12:44.175Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574, upload-time = "2023-05-22T15:12:42.313Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests-cache"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "cattrs" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "requests" },
|
||||
{ name = "url-normalize" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/b6/24aeda90d94fb1fd2cd755d6ce176e526ef61d407f87fd77de6ab0d03157/requests_cache-1.1.1.tar.gz", hash = "sha256:764f93d3fa860be72125a568c2cc8eafb151cf29b4dc2515433a56ee657e1c60", size = 2876425, upload-time = "2023-11-19T07:20:24.744Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/67/9ee2d8d8cca30f2cdc1048a4cd7dac10db2b49ec1eeca31f15a0160b71a0/requests_cache-1.1.1-py3-none-any.whl", hash = "sha256:c8420cf096f3aafde13c374979c21844752e2694ffd8710e6764685bb577ac90", size = 60260, upload-time = "2023-11-19T07:20:22.593Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roman"
|
||||
version = "4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/32/0c/10c242792e9c857d5d8df19780abec0f241c8a3d9631cccbce16d0f1c769/roman-4.1.tar.gz", hash = "sha256:4da8a200529a730822a27f1704b3ac70bc907141d3bc558115fb8e36af13b412", size = 7005, upload-time = "2023-05-26T08:25:31.645Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/e7/533d5082852a3e0001392b421172d1659a8dc81dad9c41d378adf891d689/roman-4.1-py3-none-any.whl", hash = "sha256:f131fd5eee3510f6173b825404eb97875a6c1925f73a2d7d48cfc67823a67c3c", size = 5490, upload-time = "2023-05-26T08:25:29.342Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "4.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyasn1" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "1.4.50"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/0a/dabe332c40afebb0a979d3e66b34570fce2f8611bae19b186f0c69f54643/SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf", size = 8517526, upload-time = "2023-10-29T20:32:00.003Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/ff/72d6f3261ed05b76f7d4127cfc2e5e6590cf16bd87061331ece5066cdfe8/SQLAlchemy-1.4.50-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:77fde9bf74f4659864c8e26ac08add8b084e479b9a18388e7db377afc391f926", size = 1581064, upload-time = "2024-01-11T19:15:51.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/e5/eb51bcc247017e4630bb107b15d1ceb8490f588da92b3449905231a61519/SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc", size = 1620671, upload-time = "2023-10-29T20:56:37.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/cf/4bf87f387b5360d14ef6cbcca64decf5a37ed2f6aa734724825d5146f821/SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75", size = 1619718, upload-time = "2023-10-29T20:56:32.552Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/3b/eff9b34517b4db4eadd95e86a79791deae505d18e19ea50af8ef8d1ea7a6/SQLAlchemy-1.4.50-cp312-cp312-win32.whl", hash = "sha256:e86c920b7d362cfa078c8b40e7765cbc34efb44c1007d7557920be9ddf138ec7", size = 1581637, upload-time = "2024-01-11T19:20:19.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/24/59bf0b94a619e16743e5bf51ebd10cbe97b8c946b6bd57dbf37189bd38dc/SQLAlchemy-1.4.50-cp312-cp312-win_amd64.whl", hash = "sha256:6b3df20fbbcbcd1c1d43f49ccf3eefb370499088ca251ded632b8cbaee1d497d", size = 1583598, upload-time = "2024-01-11T19:19:03.839Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url-normalize"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/80/31/febb777441e5fcdaacb4522316bf2a527c44551430a4873b052d545e3279/url_normalize-2.2.1.tar.gz", hash = "sha256:74a540a3b6eba1d95bdc610c24f2c0141639f3ba903501e61a52a8730247ff37", size = 18846, upload-time = "2025-04-26T20:37:58.553Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl", hash = "sha256:3deb687587dc91f7b25c9ae5162ffc0f057ae85d22b1e15cf5698311247f567b", size = 14728, upload-time = "2025-04-26T20:37:57.217Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wxpython"
|
||||
version = "4.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pillow" },
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/64/d749e767a8ce7bdc3d533334e03bb1106fc4e4803d16f931fada9007ee13/wxPython-4.2.1.tar.gz", hash = "sha256:e48de211a6606bf072ec3fa778771d6b746c00b7f4b970eb58728ddf56d13d5c", size = 73724359, upload-time = "2023-06-08T01:26:49.508Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/5b/55e826723af15e1d9b537c15c4e329a2921e119779aaeb2c0e792bbc5684/wxPython-4.2.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:957a6e7cc68a8e4d7ca49c72a691b6efd5684040f4f03b112d0122e7ab470497", size = 31534250, upload-time = "2023-06-08T01:24:47.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/7c/e7bff7c8d17e279386364214189d8a93ae4ef11f9a2660d4d79b536e1fcb/wxPython-4.2.1-cp312-cp312-win32.whl", hash = "sha256:8d846a785cd33c31e7eb42038eb159c88977d38208496b8322d14aef107f3eec", size = 15433640, upload-time = "2023-06-08T01:24:54.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/a4/5b4e30005b0bcf2811537869645aa8a0165fee3de9f387089e34265ca412/wxPython-4.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1ca327c877f276b33e2c4b6cb8417964305ee505e2509fb2000851d48b82328f", size = 17838517, upload-time = "2023-06-08T01:25:01.8Z" },
|
||||
]
|
||||
@@ -1 +1 @@
|
||||
version: v2.65.1
|
||||
version: v2.65.2
|
||||
|
||||
Reference in New Issue
Block a user