Home Reference Source

scripts/path-controller/controller/controller_abstract.js

import {ToolOp} from '../toolsys/toolsys.js';
import {print_stack} from '../util/util.js';
import {PropFlags, PropTypes} from '../toolsys/toolprop_abstract.js';
import {ToolProperty} from '../toolsys/toolprop.js';
import {DataPathError, isVecProperty, ListIface} from './controller_base.js';

export class ModelInterface {
  constructor() {
    this.prefix = "";
  }

  getToolDef(path) {
    throw new Error("implement me");
  }

  getToolPathHotkey(ctx, path) {
    return undefined;
  }

  get list() {
    throw new Error("implement me");
    return ListIface;
  }

  createTool(path, inputs={}, constructor_argument=undefined) {
    throw new Error("implement me");
  }

  //returns tool class, or undefined if one cannot be found for path
  parseToolPath(path) {
    throw new Error("implement me");
  }

  /**
   * runs .undo,.redo if toolstack head is same as tool
   *
   * otherwise, .execTool(ctx, tool) is called.
   *
   * @param compareInputs : check if toolstack head has identical input values, defaults to false
   * */
  execOrRedo(ctx, toolop, compareInputs=false) {
    return ctx.toolstack.execOrRedo(ctx, toolop, compareInputs);
  }

  execTool(ctx, path, inputs={}, constructor_argument=undefined) {
    return new Promise((accept, reject) => {
      let tool = path;

      try {
        if (typeof tool == "string" || !(tool instanceof ToolOp)) {
          tool = this.createTool(ctx, path, inputs, constructor_argument);
        }
      } catch (error) {
        print_stack(error);
        reject(error);
        return;
      }

      //give client a chance to change tool instance directly
      accept(tool);

      //execute
      try {
        ctx.toolstack.execTool(ctx, tool);
      } catch (error) { //for some reason chrome is suppressing errors
        print_stack(error);
        throw error;
      }
    });
  }

  //used by simple_controller.js for tagging error messages
  pushReportContext(name) {

  }

  //used by simple_controller.js for tagging error messages
  popReportContext() {

  }

  static toolRegistered(tool) {
    throw new Error("implement me");
  }

  static registerTool(tool) {
    throw new Error("implement me");
  }

  //not yet supported by path.ux's controller implementation
  massSetProp(ctx, mass_set_path, value) {
    throw new Error("implement me");
  }

  /** takes a mass_set_path and returns an array of individual paths */
  resolveMassSetPaths(ctx, mass_set_path) {
    throw new Error("implement me");
  }

  /**
   * @example
   *
   * return {
   *   obj      : [object owning property key]
   *   parent   : [parent of obj]
   *   key      : [property key]
   *   subkey   : used by flag properties, represents a key within the property
   *   value    : [value of property]
   *   prop     : [optional toolprop.ToolProperty representing the property definition]
   *   struct   : [optional datastruct representing the type, if value is an object]
   *   mass_set : mass setter string, if controller implementation supports it
   * }
   */
  resolvePath(ctx, path, ignoreExistence, rootStruct) {
  }

  setValue(ctx, path, val, rootStruct) {
    let res = this.resolvePath(ctx, path, undefined,  rootStruct);
    let prop = res.prop;

    if (prop !== undefined && (prop.flag & PropFlags.READ_ONLY)) {
      throw new DataPathError("Tried to set read only property");
    }

    if (prop !== undefined && (prop.flag & PropFlags.USE_CUSTOM_GETSET)) {
      prop.dataref = res.obj;
      prop.ctx = ctx;
      prop.datapath = path;

      if (res.subkey !== undefined) {
        let val2 = prop.getValue();
        if (typeof val2 === "object") {
          val2 = val2.copy();
        }

        if (prop.type === PropTypes.FLAG) {
          if (val) {
            val2 |= prop.values[res.subkey];
          } else {
            val2 &= ~prop.values[res.subkey];
          }

          val = val2;
        } else if (prop.type === PropTypes.ENUM) {
          val = prop.values[res.subkey];
        } else {
          val2[res.subkey] = val;
          val = val2;
        }
      }

      prop.setValue(val);
      return;
    }

    if (prop !== undefined) {
      if (prop.type === PropTypes.CURVE && !val) {
        throw new DataPathError("can't set curve data to nothing");
      }

      let use_range = (prop.type & (PropTypes.INT | PropTypes.FLOAT));

      use_range = use_range || (res.subkey && (prop.type & (PropTypes.VEC2 | PropTypes.VEC3 | PropTypes.VEC4)));
      use_range = use_range && prop.range;
      use_range = use_range && !(prop.range[0] === 0.0 && prop.range[1] === 0.0);
      use_range = use_range && typeof val === "number";

      if (use_range) {
        val = Math.min(Math.max(val, prop.range[0]), prop.range[1]);
      }
    }

    let old = res.obj[res.key];

    if (res.subkey !== undefined && res.prop !== undefined && res.prop.type === PropTypes.ENUM) {
      let ival = res.prop.values[res.subkey];

      if (val) {
        res.obj[res.key] = ival;
      }
    } else if (res.prop !== undefined && res.prop.type === PropTypes.FLAG) {
      if (res.subkey !== undefined) {
        let ival = res.prop.values[res.subkey];

        if (val) {
          res.obj[res.key] |= ival;
        } else {
          res.obj[res.key] &= ~ival;
        }
      } else if (typeof val === "number" || typeof val === "boolean") {
        val = typeof val === "boolean" ? (val & 1) : val;

        res.obj[res.key] = val;
      } else {
        throw new DataPathError("Expected a number for a bitmask property");
      }
    } else if (res.subkey !== undefined && isVecProperty(res.prop)) {
      if (res.key !== "") {
        res.obj[res.key][res.subkey] = val;
      } else {
        res.obj[res.subkey] = val;
      }
    } else if (res.key === "" && isVecProperty(res.prop)) {
      for (let i=0; i<res.obj.length; i++) {
        res.obj[i] = val[i];
      }
    } else if (!(prop !== undefined && prop instanceof ListIface)) {
      res.obj[res.key] = val;
    }

    if (prop !== undefined && prop instanceof ListIface) {
      prop.set(this, res.obj, res.key, val);
    } else if (prop !== undefined) {
      prop.dataref = res.obj;
      prop.datapath = path;
      prop.ctx = ctx;

      prop._fire("change", res.obj[res.key], old);
    }
  }

  getDescription(ctx, path) {
    let rdef = this.resolvePath(ctx, path);
    if (rdef === undefined) {
      throw new DataPathError("invalid path " + path);
    }

    if (!rdef.prop || !(rdef.prop instanceof ToolProperty)) {
      return "";
    }

    let type = rdef.prop.type;
    let prop = rdef.prop;

    if (rdef.subkey !== undefined) {
      let subkey = rdef.subkey;

      if (type & (PropTypes.VEC2|PropTypes.VEC3|PropTypes.VEC4)) {
        if (prop.descriptions && subkey in prop.descriptions) {
          return prop.descriptions[subkey];
        }
      } else if (type & (PropTypes.ENUM|PropTypes.FLAG)) {
        if (!(subkey in prop.values) && subkey in prop.keys) {
          subkey = prop.keys[subkey]
        };

        if (prop.descriptions && subkey in prop.descriptions) {
          return prop.descriptions[subkey];
        }
      } else if (type === PropTypes.PROPLIST) {
        let val = tdef.value;
        if (typeof val === "object" && val instanceof ToolProperty) {
          return val.description;
        }
      }
    }

    return rdef.prop.description ? rdef.prop.description : rdef.prop.uiname;
  }

  validPath(ctx, path, rootStruct) {
    try {
      this.getValue(ctx, path, rootStruct);
      return true;
    } catch (error) {
      if (!(error instanceof DataPathError)) {
        throw error;
      }
    }

    return false;
  }

  getPropName(ctx, path) {
    let i = path.length-1;
    while (i >= 0 && path[i] !== ".") {
      i--;
    }

    path = path.slice(i+1, path.length).trim();

    if (path.endsWith("]")) {
      i = path.length - 1;
      while (i >= 0 && path[i] !== "[") {
        i--;
      }

      path = path.slice(0, i).trim();

      return this.getPropName(ctx, path);
    }

    return path;
  }

  getValue(ctx, path, rootStruct=undefined) {
    if (typeof ctx == "string") {
      throw new Error("You forgot to pass context to getValue");
    }

    let ret = this.resolvePath(ctx, path, undefined, rootStruct);

    if (ret === undefined) {
      throw new DataPathError("invalid path " + path);
    }

    let exec = ret.prop !== undefined && (ret.prop.flag & PropFlags.USE_CUSTOM_GETSET);

    //resolvePath handles the case of vector properties with custom callbacks for us
    //(and possibly all the other cases too, need to check)
    exec = exec && !(ret.prop !== undefined && (ret.prop.type & (PropTypes.VEC2|PropTypes.VEC3|PropTypes.VEC4|PropTypes.QUAT)));

    if (exec) {
      ret.prop.dataref = ret.obj;
      ret.prop.datapath = path;
      ret.prop.ctx = ctx;

      let val = ret.prop.getValue();

      if (typeof val === "string" && (ret.prop.type & (PropTypes.FLAG|PropTypes.ENUM))) {
        val = ret.prop.values[val];
      }

      if (ret.subkey && ret.prop.type === PropTypes.ENUM) {
        val = val === ret.prop.values[ret.subkey];
      } else if (ret.subkey && ret.prop.type === PropTypes.FLAG) {
        val = val & ret.prop.values[ret.subkey];
      }

      return val;

    }

    return ret.value;
  }
}