Build Network Dataset with python comtypes

OK going to have a stab at this:

Most of your code appears to be building a Network dataset but this line

fdx = fdxc.FindExtension(esriGeoDatabase.esriDatasetType(7))  # get error here

is trying to return a GeometricNetwork (7), have a try at returning a Networkdataset (19)?


I've also created a script that creates a Network Dataset using comtypes and arcobjects COM Objects. The information posted here, these two GIS-se posts and the ArcGIS tutorial linked in the question were extremely helpful me in putting this together. I went into a little more depth in configuring the network attributes and other settings of the Network Dataset than what is in the content of the question and answers here so I'm posting my code (which is a solution to the question posed fwiw) in hopes that it may be helpful to others. The script below does import a couple of things from other python modules and those references can be seen in this github repo which hosts the larger project that this is a part of.

from datetime import datetime
from os.path import basename, join

from arcpy import env, CheckInExtension, GetInstallInfo
from comtypes.client import CreateObject, GetModule

ARCOBJECTS_DIR = join(GetInstallInfo()['InstallDir'], 'com')
GetModule(join(ARCOBJECTS_DIR, 'esriDataSourcesFile.olb'))
GetModule(join(ARCOBJECTS_DIR, 'esriGeoDatabase.olb'))
GetModule(join(ARCOBJECTS_DIR, 'esriSystem.olb'))

from comtypes.gen.esriDataSourcesGDB import FileGDBWorkspaceFactory
from comtypes.gen.esriGeoDatabase import esriDatasetType, \
    esriNetworkAttributeDataType, esriNetworkAttributeUnits, \
    esriNetworkAttributeUsageType, esriNetworkDatasetType, \
    esriNetworkEdgeConnectivityPolicy, esriNetworkEdgeDirection, \
    esriNetworkElementType, DENetworkDataset, EdgeFeatureSource, \
    EvaluatedNetworkAttribute, IDataElement, IDataset, IDatasetContainer3, \
    IDEGeoDataset, IDENetworkDataset2, IEdgeFeatureSource, IEnumDataset, \
    IEvaluatedNetworkAttribute, IFeatureDatasetExtension, \
    IFeatureDatasetExtensionContainer, IFeatureWorkspace, IGeoDataset, \
    INetworkAttribute2, INetworkBuild, INetworkDataset, \
    INetworkConstantEvaluator, INetworkEvaluator, INetworkFieldEvaluator2, \
    INetworkSource, IWorkspace, IWorkspaceFactory, IWorkspaceExtension3, \
    IWorkspaceExtensionManager, NetworkConstantEvaluator, \
    NetworkFieldEvaluator
from comtypes.gen.esriSystem import IArray, IUID, UID

from lightraildev.common import checkout_arcgis_extension, ATTRIBUTE_LEN, \
    ATTRIBUTE_MIN, ATTRIBUTE_PED, OSM_PED_FC, OSM_PED_FDS, OSM_PED_GDB, \
    OSM_PED_ND

# for some reason esriSystem.Array objects can't be created normally 
# via comtypes, I found a workaround on pg 7 of the linked pdf, below is 
# the object's GUID which can be supplied in place of the object
# http://www.pierssen.com/arcgis10/upload/python/arcmap_and_python.pdf
ARRAY_GUID = "{8F2B6061-AB00-11D2-87F4-0000F8751720}"

FDS_NAME = basename(OSM_PED_FDS)
FC_NAME = basename(OSM_PED_FC)
ND_NAME = basename(OSM_PED_ND)


def create_gdb_network_dataset():
    """"""

    # create an empty data element for a buildable network dataset
    net = new_obj(DENetworkDataset, IDENetworkDataset2)
    net.Buildable = True
    net.NetworkType = esriNetworkDatasetType(1)

    # open the feature class and ctype to the IGeoDataset interface
    gdb_ws_factory = new_obj(FileGDBWorkspaceFactory, IWorkspaceFactory)
    gdb_workspace = ctype(gdb_ws_factory.OpenFromFile(OSM_PED_GDB, 0),
                          IFeatureWorkspace)
    gdb_feat_ds = ctype(gdb_workspace.OpenFeatureDataset(FDS_NAME),
                        IGeoDataset)

    # copy the feature dataset's extent and spatial reference to the
    # network dataset data element
    net_geo_elem = ctype(net, IDEGeoDataset)
    net_geo_elem.Extent = gdb_feat_ds.Extent
    net_geo_elem.SpatialReference = gdb_feat_ds.SpatialReference

    # specify the name of the network dataset
    net_element = ctype(net, IDataElement)
    net_element.Name = ND_NAME

    edge_net = new_obj(EdgeFeatureSource, INetworkSource)
    edge_net.Name = FC_NAME
    edge_net.ElementType = esriNetworkElementType(2)

    # set the edge feature source's connectivity settings, connect
    # network through any coincident vertex
    edge_feat = ctype(edge_net, IEdgeFeatureSource)
    edge_feat.ClassConnectivityGroup = 1
    edge_feat.ClassConnectivityPolicy = esriNetworkEdgeConnectivityPolicy(0)
    edge_feat.UsesSubtypes = False

    source_array = new_obj(ARRAY_GUID, IArray)
    source_array.Add(edge_net)
    net.Sources = source_array

    add_network_attributes(net, edge_net)

    # get the feature dataset extension and create the network dataset
    # based on the data element.
    osm_data_xc = ctype(gdb_feat_ds, IFeatureDatasetExtensionContainer)
    osm_data_ext = ctype(osm_data_xc.FindExtension(esriDatasetType(19)),
                         IFeatureDatasetExtension)
    osm_data_cont = ctype(osm_data_ext, IDatasetContainer3)
    net_dataset = ctype(osm_data_cont.CreateDataset(net), INetworkDataset)

    # once the network dataset is created, build it
    net_build = ctype(net_dataset, INetworkBuild)
    net_build.BuildNetwork(net_geo_elem.Extent)


def add_network_attributes(net, edge_net):
    """"""

    attribute_array = new_obj(ARRAY_GUID, IArray)
    language = 'VBScript'

    # 1) 'ped_permissions' attribute establishes the streets and trails that
    # pedestrians can and cannot be routed along
    ped_eval_attr = new_obj(EvaluatedNetworkAttribute,
                            IEvaluatedNetworkAttribute)
    ped_attr = ctype(ped_eval_attr, INetworkAttribute2)
    ped_attr.DataType = esriNetworkAttributeDataType(3)  # boolean
    ped_attr.Name = ATTRIBUTE_PED
    ped_attr.Units = esriNetworkAttributeUnits(0)  # unknown
    ped_attr.UsageType = esriNetworkAttributeUsageType(2)  # restriction
    ped_attr.UseByDefault = True

    ped_expr = 'restricted'
    ped_logic = (
        'Set foot_list = CreateObject("System.Collections.ArrayList")'   '\n'
        'foot_list.Add "designated"'                                     '\n'
        'foot_list.Add "permissive"'                                     '\n'
        'foot_list.Add "yes"'                                          '\n\n'

        'Set hwy_list = CreateObject("System.Collections.ArrayList")'    '\n'
        'hwy_list.Add "construction"'                                    '\n'
        'hwy_list.Add "motorway"'                                        '\n'
        'hwy_list.Add "trunk"'                                         '\n\n'

        'If foot_list.Contains([foot]) Then'                             '\n'
        '    restricted = False'                                         '\n'
        'ElseIf _'                                                       '\n'
        '        ([access] = "no") Or _'                                 '\n'
        '        ([foot] = "no") Or _'                                   '\n'
        '        ([indoor] = "yes") Or _'                                '\n'
        '        (hwy_list.Contains([highway])) Then'                    '\n'
        '    restricted = True'                                          '\n'
        'Else'                                                           '\n'
        '    restricted = False'                                         '\n'
        'End If'
    )

    set_evaluator_logic(ped_eval_attr, edge_net, ped_expr, ped_logic, language)
    set_evaluator_constants(ped_eval_attr, False)
    attribute_array.Add(ped_eval_attr)

    # 2) 'length' attribute, in feet
    len_eval_attr = new_obj(EvaluatedNetworkAttribute,
                            IEvaluatedNetworkAttribute)
    len_attr = ctype(len_eval_attr, INetworkAttribute2)
    len_attr.DataType = esriNetworkAttributeDataType(2)  # double
    len_attr.Name = ATTRIBUTE_LEN
    len_attr.Units = esriNetworkAttributeUnits(3)  # feet
    len_attr.UsageType = esriNetworkAttributeUsageType(0)  # cost
    len_attr.UseByDefault = True

    len_expr = '[Shape]'
    set_evaluator_logic(len_eval_attr, edge_net, len_expr, '', language)
    set_evaluator_constants(len_eval_attr, 0)
    attribute_array.Add(len_eval_attr)

    # 3) 'minutes' attribute, assumes a walking speed of 3 mph
    min_eval_attr = new_obj(EvaluatedNetworkAttribute,
                            IEvaluatedNetworkAttribute)
    min_attr = ctype(min_eval_attr, INetworkAttribute2)
    min_attr.DataType = esriNetworkAttributeDataType(2)  # double
    min_attr.Name = ATTRIBUTE_MIN
    min_attr.Units = esriNetworkAttributeUnits(21)  # minutes
    min_attr.UsageType = esriNetworkAttributeUsageType(0)  # cost
    min_attr.UseByDefault = True

    min_expr = 'walk_minutes'
    min_logic = 'walk_minutes = [Shape] / (5280 * (3 / 60))'
    set_evaluator_logic(min_eval_attr, edge_net, min_expr, min_logic, language)
    set_evaluator_constants(min_eval_attr, 0)
    attribute_array.Add(min_eval_attr)

    net.Attributes = attribute_array


def set_evaluator_logic(eval_attr, edge_net, expression, pre_logic, lang):
    """This function implements the same logic for an attribute in
    both the along and against directions
    """

    # for esriNetworkEdgeDirection 1=along, 2=against
    for direction in (1, 2):
        net_eval = new_obj(NetworkFieldEvaluator, INetworkFieldEvaluator2)
        net_eval.SetLanguage(lang)
        net_eval.SetExpression(expression, pre_logic)
        eval_attr.Evaluator.setter(
            eval_attr, edge_net, esriNetworkEdgeDirection(direction),
            ctype(net_eval, INetworkEvaluator))


def set_evaluator_constants(eval_attr, constant):
    """This function sets all evaluator constants to the same value"""

    # for ConstantValue False means traversable (that is, not restricted),
    # for esriNetworkElementType 1=junction, 2=edge, 3=turn
    for element_type in (1, 2, 3):
        const_eval = new_obj(NetworkConstantEvaluator,
                             INetworkConstantEvaluator)
        const_eval.ConstantValue = constant
        eval_attr.DefaultEvaluator.setter(
            eval_attr, esriNetworkElementType(element_type),
            ctype(const_eval, INetworkEvaluator))


# the following two functions are derived from the linked module and make the
# code more VB.net(ic) the language from which the tutorial this code from is
# written in, all they really do is make comtypes functionality slightly less
# verbose, but that's valuable since they're called so many times
# http://www.pierssen.com/arcgis10/upload/python/snippets102.py


def new_obj(class_, interface):
    """Creates a new comtypes POINTER object where 'class_' is the class
    to be instantiated, 'interface' is the interface to be assigned
    """

    pointer = CreateObject(class_, interface=interface)
    return pointer


def ctype(obj, interface):
    """Casts obj to interface and returns comtypes POINTER"""

    pointer = obj.QueryInterface(interface)
    return pointer


def main():
    """"""

    env.overwriteOutput = True
    checkout_arcgis_extension('Network')

    create_gdb_network_dataset()
    CheckInExtension('Network')


if __name__ == '__main__':
    main()