Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions doc/source/api_reference.rst
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
API Reference
=============

FBG contain three primary modules
The following modules give access to the high level functions of FBG

* :mod:`solver`
* :mod:`layout`
* :mod:`flow`
* :mod:`solver` - generate blueprint
* :mod:`analyze` - analyze blueprint

These modules provide basic functionality.

* :mod:`layout` - elements located on a grid
* :mod:`flow` - graph representing flow between elements

Solver
------
.. automodule:: solver
:members:

Analyze
-------
.. automodule:: analyze
:members:

Layout
------
.. automodule:: layout
Expand Down
37 changes: 27 additions & 10 deletions server/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
- [TODO] expand flow graph for desired production
'''

import logging

from vector import Vector
import flow


log = logging.getLogger(__name__)

# Categorize entity types
# TODO: This should be removed once all types are supported
Expand Down Expand Up @@ -89,14 +92,15 @@


def vec_from_xydict(xydict):
'''Convert a blueprint position to a Vector'''
# Convert a blueprint position to a Vector
return Vector(xydict['x'], xydict['y'])

def vec_from_dir(dir):
return Vector(1, 0)

def extract_flow_from_site(site):
def extract_flow_from_site(site) -> flow.Graph:
'''Extract flow graph from construction site

:param site: A construction site with machines and belts

:return: A flow.Graph with constraints set from entity prototype values. You can use this as input to flow.
Expand All @@ -119,9 +123,9 @@ def extract_flow_from_site(site):
except KeyError as ex:
raise ValueError(f'Entity is incomplete: {entity}') from ex
except:
print(entity)
log.debug(entity)
raise
print(G)
log.debug(G)

# Group entities by type
entity_kind_list = {}
Expand All @@ -132,30 +136,43 @@ def extract_flow_from_site(site):
entity_kind_list[kind] = ekl
ekl.append(enr)

BELT_NORMAL = {
'transport-belt',
'fast-transport-belt',
'express-transport-belt',
}

def all_entities_of_kind(kind_set):
return [entity_id
for kind in kind_set
for entity_id in entity_kind_list.get(kind, [])
]

# Link transport belts
for belt in entity_kind_list.get('transport-belt', []):
for belt in all_entities_of_kind(BELT_NORMAL):
flow_dir = vec_from_dir(entity_dir[belt])
next_pos = entity_center[belt] + flow_dir
next_belt = center_entity.get(next_pos)
print(f'belt {belt} at {entity_center[belt]} reach for {next_pos}, found belt {next_belt}')
log.debug(f'belt {belt} at {entity_center[belt]} reach for {next_pos}, found belt {next_belt}')
if next_belt:
G.add_edge(belt, next_belt)

print('---- after belt linked up ----')
print(G)
log.debug('---- after belt linked up ----')
log.debug(G)

raise NotImplementedError()
return G

def extract_flow_from_blueprint(bp_dict):
def extract_flow_from_blueprint(bp_dict) -> flow.Graph:
'''Extract flow graph from blueprint

:param bp_dict: A blueprint dict as exported from
:return: A flow.Graph with constraints set from entity prototype values. You can use this as input to flow.
'''
assert isinstance(bp_dict, dict)
if not 'blueprint' in bp_dict:
raise ValueError('Dict does not contain a blueprint')
print(f'Blueprint content: {bp_dict["blueprint"].keys()}')
log.debug(f'Blueprint content: {bp_dict["blueprint"].keys()}')
if not 'entities' in bp_dict['blueprint']:
raise ValueError('Not a valid blueprint dict. No entities found')
entity_list = bp_dict['blueprint']['entities']
Expand Down
22 changes: 22 additions & 0 deletions server/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
'''This is a command line interface to the main functions found in the server.
It can be used for testing, or simply for scripting access to GERD featuers'''

import logging

import click

import analyze
import layout
import flow

# Set up logging
logging.basicConfig(
filename='cli.log', filemode='w',
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')
log = logging.getLogger()

def load_blueprint(filename):
'''Load and validate a string-encoded blueprint from a file.'''
Expand Down Expand Up @@ -78,6 +88,15 @@ def show_blueprint_stats(bp_dict, show_entity_details):
def gerd():
'''Command Line Interface access to some features of Gerd'''

@gerd.command
@click.argument('bp_file', type=click.types.Path(exists=True))
def decode(bp_file):
'''Decode blueprint string as JSON'''
click.echo(f'Loading blueprint from "{bp_file}"')
bp_dict = load_blueprint(bp_file)
import json
print(json.dumps(bp_dict))

@gerd.command
@click.argument('bp_file', type=click.types.Path(exists=True))
@click.option('-v', '--entity-details/--no-entity-details', default=False, help='Show entity count per type')
Expand All @@ -98,5 +117,8 @@ def maxflow(bp_file):
# Convert blueprint to flow graph
G = analyze.extract_flow_from_blueprint(bp_dict)

# Reduce flow to reflect max flow
flow.compute_max_flow(G)

if __name__ == '__main__':
gerd()
33 changes: 25 additions & 8 deletions server/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@

class Direction(IntEnum):
"""
Factorio direction enum. Encompasses all 8 cardinal directions and diagonals
Factorio direction enum. Encompasses all 16 cardinal directions and diagonals
where north is 0 and increments clockwise.

* ``NORTH`` (Default)
* ``NORTHNORTHEAST``
* ``NORTHEAST``
* ``EAST``
* ``SOUTHEAST``
Expand All @@ -22,11 +23,27 @@ class Direction(IntEnum):
* ``NORTHWEST``
"""

# Factorio 2.0 has 16 directions, before 2.0 there were 8
NORTH = 0
NORTHEAST = 1
EAST = 2
SOUTHEAST = 3
SOUTH = 4
SOUTHWEST = 5
WEST = 6
NORTHWEST = 7
NORTHNORTHEAST = 1
NORTHEAST = 2
EASTNORTHEAST = 3
EAST = 4
EASTSOUTHEAST = 5
SOUTHEAST = 6
SOUTHSOUTHEAST = 7
SOUTH = 8
SOUTHSOUTHWEST = 9
SOUTHWEST = 10
WESTSOUTHWEST = 11
WEST = 12
WESTNORTHWEST = 13
NORTHWEST = 14
NORTHNORTHWEST = 15


max_underground_length = {
'underground-belt': 4,
'fast-underground-belt': 6,
'express-underground-belt': 8,
}
49 changes: 36 additions & 13 deletions server/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
Functions related to placing machines on a grid.
'''

import logging

from constants import Direction

MACHINES_WITH_RECIPE = {
'assembling-machine-1',
'assembling-machine-2',
}

log = logging.getLogger(__name__)

class ConstructionSite:
'''Representation of the area to layout a factory'''

Expand Down Expand Up @@ -170,7 +174,7 @@ def entity_size(entity_name, direc: Direction = 0):
size = factoriocalc_entity_size(entity_name)
size = ENTITY_SIZE.get(entity_name) if size is None else size
assert size is not None, f'Unknown entity {entity_name}'
if direc in [2, 6]:
if direc in [Direction.EAST, Direction.WEST]:
# Switch x and y size
y, x = size
size = [x,y]
Expand Down Expand Up @@ -215,16 +219,19 @@ def iter_entity_area(entity_name, direc: Direction):
raise NotImplementedError(f'Unknown size of entity {entity_name}')
return iter_area(size)

def factorio_version_string_as_int():
'''return a 64 bit integer, corresponding to a version string'''
factorio_major_version = 0
factorio_minor_version = 17
factorio_patch_version = 13
factorio_dev_version = 0xffef
return (factorio_major_version << 48
| factorio_minor_version << 32
| factorio_patch_version << 16
| factorio_dev_version)
def factorio_version_string_as_int(version_string='0.17.13.65519'):
# 65519 = 0xffef = -17 in two's complement 16 bit
'''return a 64 bit integer, corresponding to a version string.'''
version_parts = [int(part) for part in version_string.split('.')]
if len(version_parts) > 4:
raise ValueError('Up to 4 parts accepted in version string')
version_int = 0
for part in version_parts:
if part < 0 or part > 0xffff:
raise ValueError('Each version string part must fit in 16 bit')
version_int = version_int << 16 | part
version_int <<= 16 * (4 - len(version_parts))
return version_int

def factorio_version_int_as_string(version):
'''return string, corresponding to a a 64 bit integer version integer from a blueprint'''
Expand Down Expand Up @@ -289,9 +296,10 @@ def export_blueprint_dict(bp_dict):

return encodedString

def import_blueprint_dict(exchangeString) -> dict:
def import_blueprint_dict(exchangeString, auto_upgrade=True) -> dict:
'''Decodes a blueprint exchange string
:param exchangeString: A blueprint exchange string
:param auto_upgrade: Determine if blueprints before factorio 2.0 should be upgraded
:return: a dict representing the blueprint
https://wiki.factorio.com/Blueprint_string_format
'''
Expand All @@ -308,8 +316,23 @@ def import_blueprint_dict(exchangeString) -> dict:
decompressedData = zlib.decompress(decodedString)
jsonString = decompressedData.decode("utf-8")
bp_dict = json.loads(jsonString)
if auto_upgrade:
upgrade_blueprint_version(bp_dict)
return bp_dict

def upgrade_blueprint_version(bp_dict) -> dict:
'''Upgrade blueprint version to the one supported.
A blueprint < 2.0 has only 8 directions, where as >= 2.0 have 16 directions'''
orig_version = bp_dict['blueprint']['version']
target_version = factorio_version_string_as_int('2.0.0.65535')
if orig_version < factorio_version_string_as_int('2.0'):
log.warning(f'Upgrading blueprint from version 0x{orig_version:016x} to 0x{target_version:016x}')
# 1.x has 8 directions, 2.x has 16 directions
for entity in bp_dict['blueprint']['entities']:
if 'direction' in entity:
entity['direction'] *= 2
bp_dict['blueprint']['version'] = target_version

def place_blueprint_on_site(site: ConstructionSite, bp_dict, offset=(0,0)):
'''Add objects from blueprint dict to construction site at the specified offset

Expand Down Expand Up @@ -340,7 +363,7 @@ def place_blueprint_on_site(site: ConstructionSite, bp_dict, offset=(0,0)):
del kwarg['entity_number']
del kwarg['name']
del kwarg['position']
print(f'Add entity {kwarg}')
log.debug(f'Add entity {kwarg}')
site.add_entity(**kwarg)

#
Expand Down
7 changes: 1 addition & 6 deletions server/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,12 +490,7 @@ def connect_machines(
else 'fast-underground-belt' if belt == 'fast-transport-belt'
else 'express-underground-belt' if belt == 'express-transport-belt'
else '<undefined-underground-belt>')
#underground_belt = 'express-underground-belt'
max_underground_length = {
'underground-belt': 4,
'fast-underground-belt': 6,
'express-underground-belt': 8,
}
from constants import max_underground_length
def pos_distance(i,j):
return ( abs(pos_list[j][0] - pos_list[i][0])
+ abs(pos_list[j][1] - pos_list[i][1]) )
Expand Down