redruin1 / factorio-draftsman

A complete, well-tested, and up-to-date module to manipulate Factorio blueprint strings. Compatible with mods.
MIT License
94 stars 17 forks source link

`ValueError: <ElectricPole>{'name': 'medium-electric-pole', ...` on Blueprint.to_string() #48

Closed rpdelaney closed 2 years ago

rpdelaney commented 2 years ago

This script:

```python """Read a blueprint, and make all Centrifuges doing kovarex request their initial ingredients.""" import sys import fileinput from draftsman.blueprintable import ( Blueprint, get_blueprintable_from_string, ) from draftsman.entity import AssemblingMachine from draftsman.classes.mixins import ModulesMixin class KovarexAssembler(AssemblingMachine, ModulesMixin): name = "centrifuge" old_bp = get_blueprintable_from_string( "\n".join(line for line in fileinput.input()), ) new_bp = Blueprint() # copy centrifuges, replacing for entity in old_bp.entities: if ( entity.name == "centrifuge" and entity.recipe == "kovarex-enrichment-process" ): new_centrifuge = KovarexAssembler() new_centrifuge.recipe = "kovarex-enrichment-process" new_centrifuge.items.update(entity.items) # ensure modules are copied new_centrifuge.items.update({"uranium-235": 40, "uranium-238": 5}) new_bp.entities.append( new_centrifuge, position=entity.position, ) print(f"{entity.position}: replaced Centrifuge", file=sys.stderr) else: new_bp.entities.append(entity) print(new_bp.to_string(), end="") ```

On this bp:

0eNq1WNtuozAU/Bc/wwpsbEh+ZRVVhDipVTDINlGjiH/fY2izpYvDxV0pD1ySOePxmeNR7uhYtrxRQhq0vyNR1FKj/e870uIi89I+M7eGoz0ShlcoQDKv7N051yY0Kpe6qZUJj7w0qAuQkCf+jvZxF8wiFFwaJc7thX/5Ie4OAYIXwgg+8Ohvbi+yrY5cAfJTBgFqag2/raUta4kk6e4XDdANLncULjvL7BsmHmMKqbky8GICLYvHaAE6CcWL4TtsApus5ZtFTyrgiQrJlKJTwOQBzKIeGGBFvzFv9TVX/D3kUonitQKYsFF1wbUGILtr2qLAo1MLRK5QPazguuQhsZwmSNEHKW3y4u25pnTlitlqTdPZHkhXY7KVnZCNK+imFMalSDTeKCGb1ryASWsFiICgxOXV+u2fIjsPe7B4Wpo4eoBW/CTaKuQlLBRaJWzqks/Y5GMJkgPlY90q6+qYBIQepkp9sbcoQZ2lZuy5jxsn+IAYxsjnYHrgtyCNXQsmGeomuWAP6zq1JOu1pM+1jAKSTfJPvnlwiaL0iaJssaLUoShdYYF0vOa6Nd88UPLzpAVin/Hg3Def+eAEzXzcih2gXiOAOE7IvyPgyHPICHN2JMPc+jw6dMP5ae7MwPGSItSzCPbpDofmmPiAujRPfDaSOkCpD2jiAGWbAlTPcfbYxKnPGKZL8gTONkQo9r8jFN5ty1DL1kwin5Z1NAKJfUAdLUuwz+hd1GOEbMlmbGU2I16GTh3qUK9wxlYECsK2hrP0x8MZ8ZoKLi0zr3DGVgRdsvMLZ+mPh7PEayA4FE3iLYmPrUx8ideE6KkfhiFto8fj75AAXUHOoV8za0acUgafJOu6P8wmkpQ=

Gives this exception at the very end:

/home/ryan/src/me/factorio-rails-cleaner/requesting_centrifuges.py:34: OverlappingObjectsWarning: Added object 'centrifuge' (KovarexAssembler) at (1.5, 1.5) intersects 'centrifuge' (KovarexAssembler) at (1.5, 1.5)
  new_bp.entities.append(
(1483.5, 1966.5): replaced Centrifuge
Traceback (most recent call last):
  File "/home/ryan/src/me/factorio-rails-cleaner/requesting_centrifuges.py", line 43, in <module>
    print(new_bp.to_string(), end="")
          └ <draftsman.classes.blueprint.Blueprint object at 0x7f7ca5577cd0>
  File "/home/ryan/src/me/factorio-rails-cleaner/.venv/lib/python3.10/site-packages/draftsman/classes/blueprint.py", line 1067, in to_string
    return utils.JSON_to_string(self.to_dict())
           │                    └ <draftsman.classes.blueprint.Blueprint object at 0x7f7ca5577cd0>
           └ <module 'draftsman.utils' from '/home/ryan/src/me/factorio-rails-cleaner/.venv/lib/python3.10/site-packages/draftsman/utils.py'>
  File "/home/ryan/src/me/factorio-rails-cleaner/.venv/lib/python3.10/site-packages/draftsman/classes/blueprint.py", line 1040, in to_dict
    neighbours[i] = flattened_list.index(neighbour()) + 1
    │          │    │                    └ <Association to ElectricPole at 0x00007F7CA3FC4F70>
    │          │    └ [<TransportBelt>{'name': 'fast-transport-belt', 'position': {'x': 1479.5, 'y': 1959.5}}, <Inserter>{'name': 'fast-inserter', 'po...
    │          └ 0
    └ [<Association to ElectricPole at 0x00007F7CA3FC4F70>, <Association to ElectricPole at 0x00007F7CA3FEEA70>]
ValueError: <ElectricPole>{'name': 'medium-electric-pole', 'position': {'x': 1485.5, 'y': 1960.5}, 'neighbours': [<Association to ElectricPole at 0x00007F7CA4662F80>, <Association to ElectricPole at 0x00007F7CA3FEF8E0>]} is not in list
redruin1 commented 2 years ago

This approach is somewhat malformed: if you add each new entity one at a time, how can you guarantee that associated entities (the wire connected ones) will even exist in the same blueprint? Here you copy everything, but Draftsman has no knowledge of this. As a result, the entities in new_bp will point to the entities in old_bp, as they have no knowledge of what entities in new_bp are their equivalent. A way to circumvent this is to set new_bp.entities = old_bp.entities, (something I needed to implement for entity merging) which preserves these associations. You can then replace only the entities you need in new_bp:

old_bp = get_blueprintable_from_string("...")
new_bp = Blueprint()
# Copy all entities (preserving their associations!)
new_bp.entities = old_bp.entities

# Replace only the centrifuges
for i, entity in enumerate(new_bp.entities):
    if (
        entity.name == "centrifuge"
        and entity.recipe == "kovarex-enrichment-process"
    ):
        new_centrifuge = AssemblingMachine("centrifuge", position=entity.position)
        new_centrifuge.recipe = "kovarex-enrichment-process"
        new_centrifuge.items.update(entity.items)  # ensure modules are copied
        new_centrifuge.items.update({"uranium-235": 40, "uranium-238": 5})

        # Place the new entity into the replaced one's position
        # (Making it the same position isn't actually necessary, but makes sense in this context)
        new_bp.entities[i] = new_centrifuge

        print(f"{entity.position}: replaced Centrifuge", file=sys.stderr)

print(new_bp.to_string(), end="")

From what I can tell from what you're trying to do though, you only need one copy of the blueprint; only protected attributes cannot be changed in entities when they currently exist in a Blueprint. Item requests are not protected, and can be changed at will:

old_bp = get_blueprintable_from_string("...")

for entity in old_bp.entities:
    if entity.name == "centrifuge":
        # Update item requests (so we preserve modules)
        entity.items.update({"uranium-235": 40, "uranium-238": 5})
        print(f"{entity.position}: replaced Centrifuge", file=sys.stderr)

print(old_bp.to_string(), end="")

Both snippets produce the same result.

image

The usage of KovarexAssembler is possible as well, though also not necessary. The usefulness of overwriting Entity classes is still in it's infancy, and was designed more along the lines of modded entities with custom interfaces. Think LTN train stops, or Space Exploration rocket launchpads, where having custom functions like launchpad.set_destination(x) or launchpad.destination = x is much nicer than launchpad.tags = {"destination": x}, which is what internal format dictates.