Sheet Syllabus Tutorial

Abstract

We can store information about objects by defining them in .ini files, then storing those definitions in a module.

Prerequisites

Json Coder
Extended Expand Path
glob
os.path.dirname
random.choices

Code

Place this code in a file called sheetSyllabus.py, located in the same directory as the blend.

# http://upbge.wikidot.com/json-coder-tutorial
import jsonCoder as json
# http://upbge.wikidot.com/extended-expand-path-tutioral
from expandPath import expandPath
 
##################
" Sheet Syllabus "
##################
class SheetSyllabus():
  def __init__(self, path='//stuff', extension='ini'):
    self._sheets = {}
    self.read(path, extension)
 
  # This is a magic method that tells the object what to do when subscripted.
  def __getitem__(self, key):  return self._sheets[key]
 
  ##----------##
  " Sheet Load "
  ##----------##
  def read(self, path='//stuff', extention='ini'):
    # glob give us a list of files based off of wildcards placed in a path.
    import glob
 
    path = expandPath(path)
    # This gives us all the .ini files in a directory and its subdirectories.
    path = path+'/**/*.'+extension
    pathList = glob.glob(path, recursive=True)
 
    # add all files found.
    for iniPath in pathList:
      self.add(iniPath)
 
  ##-----##
  " Check "
  ##-----##
  # This checks the sheet for errors.
  # There raised now with extra information so we have a better understanding the dysfunction.
  # Some of these can be modified with out error.
  def checkSheet(self, sheet, iniPath):
    for section in {'Discription', 'Path'}:
      if section not in sheet.sections():
        msg = iniPath+" :  ['"+section+"'] missing."
        raise KeyError(msg)
 
    if 'iniPath' in sheet['Path']:
      msg = iniPath+" :  ['Path']['iniPath'] is reserved for the path of the ini file."
      raise KeyError(msg)
 
    if 'thumbPath' not in sheet['Path']:
      msg = iniPath+" :  ['Path']['thumbPath'] missing."
      raise KeyError(msg)
 
    checkDisc = {'uId', 'properName', 'discription', 'typeName', 'tags', 'rndWeight'}
    for disc in checkDisc:
      if disc not in sheet['Discription']:
        msg = iniPath+" :  ['Discription']["+disc+"] missing."
        raise KeyError(msg)
 
    uId = sheet['Discription']['uId']
    if uId in self._sheets:
      duplicatePath = self[uId]['Path']['iniPath']
      msg = iniPath+" :  duplicate uId in "+ duplicatePath
      raise KeyError(msg)
 
  ##---##
  " Add "
  ##---##
  def add(self, iniPath):
    # https://docs.python.org/3/library/os.path.html#os.path.dirname
    import os.path
    workingPath = os.path.dirname(iniPath)
    # This set the working path that will be used in expandPath if the object is a path.
    json.workingPath = workingPath
 
    # https://docs.python.org/3/library/configparser.html
    import configparser
    sheet = configparser.ConfigParser()
    # This makes entries case sensitive.
    sheet.optionxform = str
    # Read the .ini file into the the ConfigParser object
    sheet.read(iniPath)
 
    # Check the sheet for errors.
    self.checkSheet(sheet, iniPath)
 
    # Our data will be stored in a dict so that the data can be converted out of a string.
    sheetDict = {}
    for section in sheet.sections():
      sheetDict[section] = {}
      for subsection in sheet[section]:
        msg = sheet[section][subsection]
        # We do some error checking so that we have better knowledge of what any problems are.
        try:  msg = json.loads(msg)
        except KeyError:
          msg = iniPath+' :  unreconized entry in ['+section+']['+subsection+']; '+msg
          raise KeyError(msg)
        except FileNotFoundError:
          msg = iniPath+' :  missing file in ['+section+']['+subsection+']; '+msg
          raise FileNotFoundError(msg)
        # Set the converted object into the dict.
        sheetDict[section][subsection] = msg
 
    # Put the path to the ini file in a hard coded location.
    sheetDict['Path']['iniPath'] = iniPath
 
    # Store the dict representing the sheet internally.
    uId = sheetDict['Discription']['uId']
    self._sheets[uId] = sheetDict
 
    # Turn off the workingPath so its not used unexpectedly.
    json.workingPath = None
 
  ##------------##
  " Random Table "
  ##------------##
  # Sometimes it may be performant to precache the random table.
  def rngTable(self, all=set(), any=set(), none=set()):
    """
    returns a uIds and preComputedWeights pair for self.rng or random.choices 
    """
    uIds = []
    weights = []
    tally = 0
    # Get every sheet.
    for sheet in self._sheets.values():
      # Determine there tags.
      tags = set( sheet['Discription']['tags'] )
      # All the all tags and none of the none tags.
      if all <= tags and tags.isdisjoint(none):
        # If theres anything in any and you have at least one any.
        if len(any) == 0 or not tags.isdisjoint(any):
          # Add to both the uIs and weights.
          uIds.append( sheet['Discription']['uId'] )
          tally += sheet['Discription']['rndWeight']
          weights.append(tally)
    return uIds, weights
 
  ##-------------##
  " Random Choice "
  ##-------------##
  def rng(self, all=set(), any=set(), none=set(), num=1, table=False):
    """
    all; must have all the all tags and,
    any; must have at least one of the any tags unless any is empty and,
    none; must have none of the none tags.
 
    num: how many to return
    table: a pre computed table from self.rngTable
    """
    # If no table was provide then make one.
    if not table:  table = self.rngTable(all, any, none)
    from random import choices
    # https://docs.python.org/3/library/random.html#random.choices
    # Randomly selects uIds with replacement.  table[0] is the uIds and table[1] the weights from rngTable.
    return choices(table[0], k=num, cum_weights=table[1])
 
# Make a python module subscriptable
# https://sohliloquies.blogspot.com/2017/07/how-to-make-subscriptable-module-in.html
import sys
sys.modules[__name__] = SheetSyllabus()

Example .ini

[Discription]
uId = "clothChainHeavyTop"
properName = "Heavy Chain Leggings"
discription = "A pair of heavy chain leggings."
typeName = "cloth"
rndWeight = 1
tags = ["armor", "pants"]

[Path]
thumbPath = {"Path": "//system/NoImage.png"}
blendPaths = [{"Path": "clothChainHeavy.blend"}]

[Mesh]
mesh = "clothChainHeavy_Bot"
slots = ["Leg"]

Usage

Make sure you have the following files.
//expandPath.py
//jsonCoder.py
//sheetSyllabus.py
//libaryLoader.py
//stuff/someObject.ini

import sheetSyllabus as sheets
import libaryLoader as loader
import bge
own = bge.logic.getCurrentController.owner
 
uId = 'clothChainHeavyTop'
 
# Get a sheet by its uId.
sheet = sheets[uId]
 
# Load the .blends in sheet['Path']['blendPaths'] by uId.
loader.add(uId)
 
# Get the name of the new mesh from the sheet.
mesh = sheets['Mesh']['mesh']
 
# Replace the mesh on owns pants attribute.
own.pants.replaceMesh(mesh)

Questions

Log in and edit this page to ask and answer questions.

Further Reading

Magic Method
Make a Subscriptable Module
os.path
random

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License