factorio-draftsman

repository·main·Indexed 41 days ago

https://github.com/redruin1/factorio-draftsman

A Python module for the programmatic creation, editing, and manipulation of Factorio blueprint strings. It provides tools for automating complex designs, including a RailPlanner for rail layouts, image-to-blueprint conversion, and support for modded entities. Features include set operations on blueprint entities, train schedule construction, and a CLI tool for managing mod environments and Factorio data versions.

Tokens
41.8K
Snippets
179
Records
223
Agent score
52%

What's inside factorio-draftsman

  1. Overview of factorio-draftsman

    main

    factorio-draftsman

    factorio-draftsman is a Python module designed for creating, manipulating, and exporting Factorio Blueprint strings. It provides a programmatic way to handle blueprint data without manual string manipulation.

    Key Capabilities:

    • Blueprint Manipulation: Import, modify, create, add, remove, and export blueprints and their contents.
    • Entity Management: Use unique entity IDs and dynamic associations between entities.
    • Advanced Querying: Search blueprint contents by area, name, type, and other criteria.
    • Structural Organization: Group entities together to treat them as single units for reuse in design.
    • Data Fidelity: Emulates the Factorio data lifecycle by extracting data directly from Wube's public repository. This ensures compatibility with Factorio versions and provides built-in support for mods.
    • Error Handling: Provides verbose warnings for modifications on import that would normally be ignored by the game.

    Compatibility:

    • Python: Version 3.10 and higher.
    • Factorio: Version 1.0 and up (functionality for older versions is not guaranteed).
  2. Explore the Draftsman module structure

    main

    Draftsman organizes its functionality into several specialized submodules. Depending on your goal, you should look into the following areas:

    • Blueprintable objects: For working with Blueprint, BlueprintBook, UpgradePlanner, DeconstructionPlanner, and Group.
    • Entities: For all Entity implementations and their abstract base classes.
    • Prototypes: For individual Entity prototype definitions.
    • Environment: For modules that extract data from Factorio and store it as a Draftsman environment.
    • Data: For modules that read and modify the Draftsman environment.
    • Constants: For enumerations used when defining blueprints or entities.
    • Signatures: For abstract data formats used across multiple class implementations.
    • Tiles: For the Tile class and related methods.
    • Extras: For specialized features (e.g., reversing all belts in a blueprint).
    • Utilities: For generic utility functions.
    • Errors and Warnings: For exception and warning types issued by the library.
  3. Manipulate Factorio Lua data with draftsman.environment

    main

    The draftsman.environment module provides tools and methods for manipulating the set of data that Draftsman extracts from Factorio's Lua data. It is used to manage the underlying data environment used during blueprint generation and extraction processes.

    For command-line usage of the draftsman utility, refer to the draftsman.environment.script documentation.

  4. What is Draftsman's Environment?

    main

    Draftsman's 'environment' is a collection of extracted metadata (stored as pickle files in site-packages/draftsman/data) derived from Factorio's data.raw Lua tables. This environment provides the contextual information necessary for Draftsman to perform advanced tasks such as:

    • Automatic Type Deduction: Inferring entity types from names.
    • Validation: Warning if productivity modules are placed in machines that don't support them.
    • Autocorrect: Suggesting correct names (e.g., suggesting wooden-chest for woden-chest).
    • Prototype Access: Accessing raw game data like collision boxes, icon paths, and inventory sizes.

    By default, Draftsman ships with pregenerated data, but this data must be manually updated to match new Factorio versions or specific mod configurations.

    from draftsman.entity import Container
    
    # Accessing raw prototype data from the environment
    chest = Container("wooden-chest")
    print(chest.prototype.keys())
    # dict_keys(['name', 'type', 'collision_box', ..., 'max_health'])
    
    # Accessing graphics information
    print(chest.prototype["picture"])
  5. Use Groups and Tiles in Blueprints

    main

    In Draftsman 3.0, Group objects have been updated to explicitly handle both entities and tiles.

    Adding Tiles to Groups

    Instead of nesting tiles inside entities, you can now add tiles directly to a Group. When exported, these are flattened into the blueprint structure.

    Accessing Entities via Paths

    Even though the tree structure is more explicit, you can still use tuples of (str|int) to represent entity paths for operations like add_circuit_connection().

    Example path: ("group_name", "entity_name")

    from draftsman import Blueprint, Group
    
    group = Group()
    group.entities.append("wooden-chest")
    group.tiles.append("refined-concrete")
    
    blueprint = Blueprint()
    blueprint.groups.append(group, position=(0, 0))
    
    # Using paths for connections
    blueprint.add_circuit_connection("red", ("group1", "entityA"), ("group2", "entityB"))
  6. Manage Entity positions using absolute and tile coordinates

    main

    Draftsman uses two coordinate systems that stay in sync:

    1. position: Floating point coordinates representing the actual center of the entity.
    2. tile_position: Integer coordinates representing the top-leftmost tile covered by the entity.

    Usage:

    • You can set either position or tile_position, and the other will update automatically.
    • You can pass a draftsman.utils.Vector, a tuple, or a list to these attributes.
    • If no position is specified, it defaults to tile_position (0, 0), with the absolute position deduced from the entity's tile dimensions.
    from draftsman.entity import Container
    from draftsman.utils import Vector
    
    container = Container("steel-chest")
    
    # Using Vector
    container.position = Vector(10.5, 10.5)
    
    # Using tuple/list (updates tile_position)
    container.position = (15.5, 45.5)
    container.tile_position = [2, 3]
    
    # Setting in constructor
    container2 = Container("iron-chest", position=[10.5, 15.5])
  7. Enforce unique Entity IDs

    main

    In Blueprints, all entity IDs must be unique. If you attempt to add an entity with an ID that is already taken within that Blueprint, a DuplicateIDError will be raised.

    Note: If you create an Entity outside of a Blueprint, the uniqueness rule is only enforced when that entity is eventually added to a Blueprint that already contains an entity with that same ID.

    blueprint.entities.append("inserter", id="same")
    blueprint.entities.append("inserter", id="same") # Raises DuplicateIDError: 'same'
    
    # Outside of blueprints, the rule is only enforced upon addition
    outside_inserter = Inserter("inserter", id="same")
    blueprint.entities.append(outside_inserter) # Raises DuplicateIDError: 'same'
  8. How Associations handle entity connections

    main

    In Draftsman, connections (like circuit wires) between entities are managed using Associations rather than static integer indices.

    In standard Factorio blueprint strings, connections use the index of the entity in the entities list. However, if you insert or delete entities, these indices shift, which would break connections or cause them to point to the wrong entity.

    Draftsman solves this by using Associations, which are wrappers around weakref.ref that point directly to the memory location of the Entity object. This ensures that:

    1. Stability: Moving or inserting entities does not break connections because the reference follows the object, not its position in a list.
    2. Data Integrity: Changes made to an entity (e.g., changing its id) are immediately reflected through the association.
    3. Memory Safety: Because they use weakref, an association won't prevent an entity from being garbage collected if it is deleted from the blueprint.

    When exporting to a Factorio blueprint string, these Associations are automatically converted back into the numeric indices required by the Factorio format.

    blueprint = Blueprint()
    blueprint.entities.append("inserter", tile_position=[-1, 0])
    blueprint.entities.append("inserter", tile_position=[+1, 0])
    
    # Uses indices initially, but internally creates Associations
    blueprint.add_circuit_connection("red", 0, 1)
    
    # Inserting an entity in the middle does NOT break the connection
    blueprint.entities.insert(1, "wooden-chest", tile_position=(0, 0))
  9. Understand the Draftsman Environment dependency

    main

    The Draftsman 'Environment' is an optional layer of contextual information. While not strictly required for the module to function, the environment provides the necessary context to distinguish between specific entity types (e.g., distinguishing a Container from an Inserter or an AgriculturalTower).

    Behavior without an Environment:

    • If you delete all pickle files in the data folder, Draftsman will still run.
    • Any imported blueprint string will default to making each entity a generic .Entity instance.
    • You lose the ability to use type-specific features and benefits that rely on knowing the exact entity class.

    If Draftsman fails to operate when the environment is missing, it is considered a bug.

  10. Handle entity collisions with CollisionSet

    main

    The collision_box attribute has been replaced by collision_set, which is an instance of CollisionSet.

    Unlike the previous axis-aligned collision_box, a CollisionSet contains a list of shapes. Intersection tests are performed against the union of these shapes. This allows for more accurate collision detection, including non-axis-aligned shapes (via utils.Rectangle) and multiple collision objects per entity (common for rails and signals).

    Extending collisions: To add custom collision shapes to a custom EntityLike, create a child class of utils.Shape, implement the required functions, and add it to the collision_set.

  11. Nesting Groups and calculating dimensions

    main

    Groups can contain other groups via the groups attribute, allowing for tree-like structures. The dimensions of a parent group are calculated based on the bounding box of its entities, tiles, and all nested child groups.

    To use a group in a top-level blueprint, append it to the Blueprint.groups list.

    from draftsman.blueprintable import Group, Blueprint
    
    child_group = Group()
    child_group.entities.append("iron-chest", tile_position=(2, 2))
    child_group.tiles.append("concrete", tile_position=(2, 2))
    
    parent_group = Group()
    parent_group.groups.append(child_group)
    
    # parent_group.get_dimensions() will account for the child's position
    
    blueprint = Blueprint()
    blueprint.groups.append(parent_group)
  12. Understand the Collection base class

    main
    The Collection class serves as the fundamental base class for major organizational structures in factorio-draftsman. Specifically, it is used as the parent class for Blueprint and Group objects. While you may not instantiate Collection directly, understanding it is key to understanding how blueprints and groups share common behaviors and properties within the library.