diff --git a/README.md b/README.md index 29f5f74..2c7b27b 100644 --- a/README.md +++ b/README.md @@ -497,17 +497,25 @@ Declarations might look like this: ```yaml cases: case_name: - - outline: + - type: outline + name: extrude: num # default = 1 shift: [x, y, z] # default = [0, 0, 0] rotate: [ax, ay, az] # default = [0, 0, 0] operation: add | subtract | intersect # default = add + - type: case + name: + # extrude makes no sense here... + shift: # same as above + rotate: # same as above + operation: # same as above - ... ... ``` -`outline` specifies which outline to import onto the xy plane, while `extrude` specifies how much it should be extruded along the z axis. -After that, the object is `shift`d, `rotate`d, and combined with what we have so far according to `operation`. +When the `type` is `outline`, `name` specifies which outline to import onto the xy plane, while `extrude` specifies how much it should be extruded along the z axis. +When the `type` is `case`, `name` specifies which case to use. +After having established our base 3D object, it is (relatively!) `rotate`d, `shift`ed, and combined with what we have so far according to `operation`. If we only want to use an object as a building block for further objects, we can employ the same "start with an underscore" trick we learned at the outlines section to make it "private". @@ -543,6 +551,7 @@ If we only want to use an object as a building block for further objects, we can + ## A concrete case example diff --git a/src/cases.js b/src/cases.js index 80b6b31..8ec6a3e 100644 --- a/src/cases.js +++ b/src/cases.js @@ -4,68 +4,120 @@ const a = require('./assert') exports.parse = (config, outlines) => { - const cases = a.sane(config, 'cases', 'object') + const cases_config = a.sane(config, 'cases', 'object') + + const scripts = {} + const cases = {} const results = {} - for (const [case_name, case_config] of Object.entries(cases)) { + const resolve = (case_name, resolved_scripts=new Set(), resolved_cases=new Set()) => { + for (const o of Object.values(cases[case_name].outline_dependencies)) { + resolved_scripts.add(o) + } + for (const c of Object.values(cases[case_name].case_dependencies)) { + resolved_cases.add(c) + resolve(c, resolved_scripts, resolved_cases) + } + result = [] + for (const o of resolved_scripts) { + result.push(scripts[o] + '\n\n') + } + for (const c of resolved_cases) { + result.push(cases[c].body) + } + result.push(cases[case_name].body) + result.push(` + + function main() { + return ${case_name}_case_fn(); + } + + `) + return result.join('') + } + + for (const [case_name, case_config] of Object.entries(cases_config)) { // config sanitization const parts = a.sane(case_config, `cases.${case_name}`, 'array') - const scripts = [] - const main = [] - + const body = [] + const case_dependencies = [] + const outline_dependencies = [] let part_index = 0 for (const part of parts) { const part_name = `cases.${case_name}[${++part_index}]` - a.detect_unexpected(part, part_name, ['outline', 'extrude', 'shift', 'rotate', 'operation']) - const outline = outlines[part.outline] - a.assert(outline, `Field ${part_name}.outline does not name a valid outline!`) - const extrude = a.sane(part.extrude || 1, `${part_name}.extrude`, 'number') + const part_var = `${case_name}__part_${part_index}` + a.detect_unexpected(part, part_name, ['type', 'name', 'extrude', 'shift', 'rotate', 'operation']) + const type = a.in(part.type, `${part_name}.type`, ['outline', 'case']) + const name = a.sane(part.name, `${part_name}.name`, 'string') const shift = a.numarr(part.shift || [0, 0, 0], `${part_name}.shift`, 3) const rotate = a.numarr(part.rotate || [0, 0, 0], `${part_name}.rotate`, 3) const operation = a.in(part.operation || 'add', `${part_name}.operation`, ['add', 'subtract', 'intersect']) + let base + if (type == 'outline') { + const extrude = a.sane(part.extrude || 1, `${part_name}.extrude`, 'number') + const outline = outlines[name] + a.assert(outline, `Field "${part_name}.name" does not name a valid outline!`) + if (!scripts[name]) { + scripts[name] = m.exporter.toJscadScript(outline, { + functionName: `${name}_outline_fn`, + extrude: extrude, + indent: 4 + }) + } + outline_dependencies.push(name) + base = `${name}_outline_fn()` + } else { + a.assert(part.extrude === undefined, `Field "${part_name}.extrude" should not be used when type=case!`) + a.in(name, `${part_name}.name`, Object.keys(cases)) + case_dependencies.push(name) + base = `${name}_case_fn()` + } + let op = 'union' if (operation == 'subtract') op = 'subtract' else if (operation == 'intersect') op = 'intersect' - const part_fn = `${part.outline}_fn` - const part_var = `${part.outline}_var` - - scripts.push(m.exporter.toJscadScript(outline, { - functionName: part_fn, - extrude: extrude - })) - - let op_statement = `let ${case_name} = ${part_var};` + let op_statement = `let result = ${part_var};` if (part_index > 1) { - op_statement = `${case_name} = ${case_name}.${op}(${part_var});` + op_statement = `result = result.${op}(${part_var});` } - main.push(` + body.push(` // creating part ${part_index} of case ${case_name} - let ${part_var} = ${part_fn}(); + let ${part_var} = ${base}; + + // make sure that rotations are relative + let ${part_var}_bounds = ${part_var}.getBounds(); + let ${part_var}_x = ${part_var}_bounds[0].x + (${part_var}_bounds[1].x - ${part_var}_bounds[0].x) / 2 + let ${part_var}_y = ${part_var}_bounds[0].y + (${part_var}_bounds[1].y - ${part_var}_bounds[0].y) / 2 + ${part_var} = translate([-${part_var}_x, -${part_var}_y, 0], ${part_var}); ${part_var} = rotate(${JSON.stringify(rotate)}, ${part_var}); + ${part_var} = translate([${part_var}_x, ${part_var}_y, 0], ${part_var}); + ${part_var} = translate(${JSON.stringify(shift)}, ${part_var}); ${op_statement} `) } - results[case_name] = ` + cases[case_name] = { + body: ` - // individual makerjs exports - ${scripts.join('\n\n')} + function ${case_name}_case_fn() { + ${body.join('')} + return result; + } + + `, + case_dependencies, + outline_dependencies + } - // combination of parts - function main() { - ${main.join('')} - return ${case_name}; - } - - ` + results[case_name] = resolve(case_name) } return results