| … | … | @@ -117,27 +117,61 @@ static func _expand_one(p: Dictionary, doc: Dictionary, parse: Callable, out: Ar |
| 117 | 117 | var pa := String(p["author"]) |
| 118 | 118 | var ps := int(p["seq"]) |
| 119 | 119 | var pdot := String(p["dot"]) |
| 120 | | var m := placement_xform(p["frame"], doc["pivot"]) |
| 120 | var names := local_names(doc) |
| 121 | var dot_of := func(local: String) -> String: |
| 122 | return "%s/A%04d:%d" % [pa, int(names[local]), ps] if names.has(local) else "" |
| 121 | 123 | var moved: bool = int(p["ver"]) > ps |
| 122 | | var names := {} # "L:<n>" -> synthetic author |
| 124 | for e: Dictionary in payloads(p, doc, dot_of): |
| 125 | var local := String(e["local"]) |
| 126 | var au := "%s/A%04d%s" % [pa, int(names[local]), String(e["suffix"])] |
| 127 | var pl: Dictionary = e["payload"] |
| 128 | var rec: Dictionary = parse.call(au, ps, pl) |
| 129 | if rec.is_empty(): |
| 130 | continue |
| 131 | rec["inst"] = pdot |
| 132 | out.append(rec) |
| 133 | match String(pl["kind"]): |
| 134 | "shape.create": |
| 135 | inst[dot_of.call(local)] = pdot |
| 136 | if moved: |
| 137 | # Same frame, later seq: ShapeFold's <ver> rule renames the pieces. |
| 138 | var xr: Dictionary = parse.call(au, int(p["ver"]), {"kind": "shape.xform", |
| 139 | "target": dot_of.call(local), "frame": pl["frame"]}) |
| 140 | if not xr.is_empty(): |
| 141 | xr["inst"] = pdot |
| 142 | out.append(xr) |
| 143 | "brush.create": |
| 144 | inst[String(pl["group"])] = pdot |
| 145 | "object.create": |
| 146 | inst[dot_of.call(local)] = pdot |
| 147 | |
| 148 | |
| 149 | ## Local name -> its number: every record's dot, and every brush object's local GROUP (a |
| 150 | ## name its brush.create records share, not a record of its own — it needs a name too: "" |
| 151 | ## would merge it with every ungrouped brush in the level). |
| 152 | static func local_names(doc: Dictionary) -> Dictionary: |
| 153 | var names := {} |
| 123 | 154 | for r: Dictionary in (doc["records"] as Array): |
| 124 | | var n := int(String(r["dot"]).substr(2)) |
| 125 | | names[String(r["dot"])] = "%s/A%04d" % [pa, n] |
| 126 | | # A brush object's GROUP is a local name its brush.create records share, not a |
| 127 | | # record of its own: it needs a name too ("" would merge it with every ungrouped |
| 128 | | # brush in the level). |
| 155 | names[String(r["dot"])] = int(String(r["dot"]).substr(2)) |
| 129 | 156 | var lg := String(r.get("group", "")) if String(r["kind"]) == "brush.create" else "" |
| 130 | 157 | if lg.begins_with("L:") and not names.has(lg): |
| 131 | | names[lg] = "%s/A%04d" % [pa, int(lg.substr(2))] |
| 132 | | var dot_of := func(local: String) -> String: |
| 133 | | return "%s:%d" % [names[local], ps] if names.has(local) else "" |
| 134 | | var emit := func(author: String, seq: int, payload: Dictionary) -> void: |
| 135 | | var rec: Dictionary = parse.call(author, seq, payload) |
| 136 | | if not rec.is_empty(): |
| 137 | | rec["inst"] = pdot |
| 138 | | out.append(rec) |
| 158 | names[lg] = int(lg.substr(2)) |
| 159 | return names |
| 160 | |
| 161 | |
| 162 | ## A placement's records as journal PAYLOADS, M applied and every local reference resolved |
| 163 | ## by `dot_of` (local name -> the dot it becomes): [{local, suffix, payload}], in the blob's |
| 164 | ## order. `suffix` "" = the local's own record; "r" / "t" a brush object's turn / move, |
| 165 | ## "s" a modifier's or cut's off switch, "p" / "q" an object's parent / props — follow-ups |
| 166 | ## that must sort after it. SHARED by the fold (synthetic dots) and Make Real (fresh dots), |
| 167 | ## so the two cannot drift. |
| 168 | static func payloads(p: Dictionary, doc: Dictionary, dot_of: Callable) -> Array: |
| 169 | var out: Array = [] |
| 170 | var m := placement_xform(p["frame"], doc["pivot"]) |
| 171 | var add := func(local: String, suffix: String, payload: Dictionary) -> void: |
| 172 | out.append({"local": local, "suffix": suffix, "payload": payload}) |
| 139 | 173 | for r: Dictionary in (doc["records"] as Array): |
| 140 | | var au: String = names[String(r["dot"])] |
| 174 | var local := String(r["dot"]) |
| 141 | 175 | var kind := String(r["kind"]) |
| 142 | 176 | var pl: Dictionary = r.duplicate(true) |
| 143 | 177 | pl.erase("dot") |
| … | … | @@ -152,10 +186,9 @@ static func _expand_one(p: Dictionary, doc: Dictionary, parse: Callable, out: Ar |
| 152 | 186 | q2[hk] = _arr(m.basis * _v3(q[hk])) |
| 153 | 187 | pts.append(q2) |
| 154 | 188 | pl["points"] = pts |
| 155 | | emit.call(au, ps, pl) |
| 189 | add.call(local, "", pl) |
| 156 | 190 | "shape.create": |
| 157 | | var f := m * SceneObjects.xform_from(pl["frame"]) |
| 158 | | pl["frame"] = SceneObjects.xform_to(f) |
| 191 | pl["frame"] = SceneObjects.xform_to(m * SceneObjects.xform_from(pl["frame"])) |
| 159 | 192 | var sp: Dictionary = pl["params"] |
| 160 | 193 | for ck: String in ["curve", "profile_curve"]: |
| 161 | 194 | if sp.has(ck): |
| … | … | @@ -164,12 +197,7 @@ static func _expand_one(p: Dictionary, doc: Dictionary, parse: Callable, out: Ar |
| 164 | 197 | pl["link"] = dot_of.call(String(pl["link"])) |
| 165 | 198 | else: |
| 166 | 199 | pl.erase("link") |
| 167 | | emit.call(au, ps, pl) |
| 168 | | inst[dot_of.call(String(r["dot"]))] = pdot |
| 169 | | if moved: |
| 170 | | # Same frame, later seq: ShapeFold's <ver> rule renames the pieces. |
| 171 | | emit.call(au, int(p["ver"]), {"kind": "shape.xform", "target": dot_of.call(String(r["dot"])), |
| 172 | | "frame": pl["frame"]}) |
| 200 | add.call(local, "", pl) |
| 173 | 201 | "mod.create": |
| 174 | 202 | var mp: Dictionary = pl["params"] |
| 175 | 203 | if mp.has("curve"): |
| … | … | @@ -179,9 +207,9 @@ static func _expand_one(p: Dictionary, doc: Dictionary, parse: Callable, out: Ar |
| 179 | 207 | pl["target"] = dot_of.call(String(pl["target"])) |
| 180 | 208 | var on := bool(pl.get("on", true)) |
| 181 | 209 | pl.erase("on") |
| 182 | | emit.call(au, ps, pl) |
| 210 | add.call(local, "", pl) |
| 183 | 211 | if not on: |
| 184 | | emit.call(au + "s", ps, {"kind": "mod.set", "target": dot_of.call(String(r["dot"])), "on": false}) |
| 212 | add.call(local, "s", {"kind": "mod.set", "target": dot_of.call(local), "on": false}) |
| 185 | 213 | "op.create": |
| 186 | 214 | pl["cutter"] = dot_of.call(String(pl["cutter"])) |
| 187 | 215 | var tg: Array = [] |
| … | … | @@ -190,39 +218,38 @@ static func _expand_one(p: Dictionary, doc: Dictionary, parse: Callable, out: Ar |
| 190 | 218 | pl["targets"] = tg |
| 191 | 219 | var on2 := bool(pl.get("on", true)) |
| 192 | 220 | pl.erase("on") |
| 193 | | emit.call(au, ps, pl) |
| 221 | add.call(local, "", pl) |
| 194 | 222 | if not on2: |
| 195 | | emit.call(au + "s", ps, {"kind": "op.set", "target": dot_of.call(String(r["dot"])), "on": false}) |
| 223 | add.call(local, "s", {"kind": "op.set", "target": dot_of.call(local), "on": false}) |
| 196 | 224 | "brush.create": |
| 197 | 225 | pl["group"] = dot_of.call(String(pl["group"])) |
| 198 | | inst[String(pl["group"])] = pdot |
| 199 | | emit.call(au, ps, pl) |
| 200 | | var key: String = dot_of.call(String(r["dot"])) |
| 226 | add.call(local, "", pl) |
| 227 | var key: String = dot_of.call(local) |
| 201 | 228 | # Turned about the world origin by M's basis, then moved by M's origin: the |
| 202 | | # same map as M, through the store's texture lock. Identity emits nothing. |
| 229 | # same map as M, through the store's texture lock. Identity adds nothing. |
| 203 | 230 | var q3 := m.basis.get_rotation_quaternion() |
| 204 | 231 | var ang := rad_to_deg(q3.get_angle()) |
| 205 | 232 | if absf(ang) > 1.0e-9: |
| 206 | | emit.call(au + "r", ps, {"kind": "brush.rotate", "brush_key": key, "axis": _arr(q3.get_axis()), |
| 233 | add.call(local, "r", {"kind": "brush.rotate", "brush_key": key, "axis": _arr(q3.get_axis()), |
| 207 | 234 | "angle": ang, "pivot": [0, 0, 0], "tex_lock": true}) |
| 208 | 235 | if m.origin != Vector3.ZERO: |
| 209 | | emit.call(au + "t", ps, {"kind": "brush.translate", "brush_key": key, "delta": _arr(m.origin), |
| 236 | add.call(local, "t", {"kind": "brush.translate", "brush_key": key, "delta": _arr(m.origin), |
| 210 | 237 | "tex_lock": true}) |
| 211 | 238 | "object.create": |
| 212 | 239 | var par := String(pl.get("parent", "")) |
| 213 | 240 | var x := SceneObjects.xform_from(pl["xform"]) |
| 214 | 241 | var props: Dictionary = pl.get("props", {}) |
| 215 | | emit.call(au, ps, {"kind": "object.create", "type": pl["type"], |
| 242 | add.call(local, "", {"kind": "object.create", "type": pl["type"], |
| 216 | 243 | "xform": SceneObjects.xform_to(x if par != "" else m * x)}) |
| 217 | | inst[dot_of.call(String(r["dot"]))] = pdot |
| 218 | 244 | if par != "": |
| 219 | | emit.call(au + "p", ps, {"kind": "object.parent", "target": dot_of.call(String(r["dot"])), |
| 245 | add.call(local, "p", {"kind": "object.parent", "target": dot_of.call(local), |
| 220 | 246 | "parent": dot_of.call(par), "xform": SceneObjects.xform_to(x)}) |
| 221 | 247 | if not props.is_empty(): |
| 222 | | emit.call(au + "q", ps, {"kind": "object.set_prop", "target": dot_of.call(String(r["dot"])), |
| 248 | add.call(local, "q", {"kind": "object.set_prop", "target": dot_of.call(local), |
| 223 | 249 | "props": props.duplicate(true)}) |
| 224 | 250 | _: |
| 225 | 251 | pass # col.create: organisation, not geometry |
| 252 | return out |
| 226 | 253 | |
| 227 | 254 | |
| 228 | 255 | ## The wire box of a placement whose blob is not here: bounds unknown too, so a 1 m cube at |