Write every model that moved, and say which ones are waiting
`Write to source` saved the selected row and only that. Nudge three things into place, click the third and press it, and two of them were still sitting in memory with nothing on screen to say so - which reads exactly like a button that does not save, because the file you go and look at is usually not the one that was selected when you pressed it. So it writes everything that has moved. A row whose model no longer matches what its module declares is marked with a `*` until it has been written, so what is pending is on the list rather than in your memory, and the report says how many files were written and which. A model no module names - a sword part, a gem case, an icon of its own - has nowhere to put a transform, and is now named in that report rather than passed over in silence: it keeps its mark, because it really is moved and really is unsaved. Nothing about the writing itself changed. It was tested one model at a time, which is the one case where the old behaviour was indistinguishable from the new. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>main
parent
8e5178ece6
commit
8f253a848a
|
|
@ -465,8 +465,10 @@ model's origin move it along an axis and the rotate toggle swaps them for three
|
||||||
rings; the position and rotation boxes read what the drag did, in the units a
|
rings; the position and rotation boxes read what the drag did, in the units a
|
||||||
module writes, and take typed numbers back. `Copy as JSON` puts the `transform`
|
module writes, and take typed numbers back. `Copy as JSON` puts the `transform`
|
||||||
block on the clipboard, and `Write to source` puts it back in the module entry
|
block on the clipboard, and `Write to source` puts it back in the module entry
|
||||||
it was read from - scale and origin untouched, and nothing to write for a model
|
it was read from - scale and origin untouched. It writes every model that has
|
||||||
no module names. `Unwrap UVs` re-cuts the selected model's texture so every
|
moved rather than just the selected one, and marks a moved row with a `*` until
|
||||||
|
it has; a model no module names has nowhere to write to and is named in the
|
||||||
|
report instead. `Unwrap UVs` re-cuts the selected model's texture so every
|
||||||
face has a patch of its own, writes the atlas size beside the boxes, and paints
|
face has a patch of its own, writes the atlas size beside the boxes, and paints
|
||||||
a template to match where there is no texture yet - never over one there is.
|
a template to match where there is no texture yet - never over one there is.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,15 @@ toggle swaps them for three rings. Dragging one and typing a number are the
|
||||||
same edit seen from two sides, so the panel always reads what the model is
|
same edit seen from two sides, so the panel always reads what the model is
|
||||||
doing, and `Copy as JSON` hands over the block to paste into the module.
|
doing, and `Copy as JSON` hands over the block to paste into the module.
|
||||||
|
|
||||||
`Write to source` is the one thing here that changes a file: it puts those
|
`Write to source` is the one thing here that changes a module file. It writes
|
||||||
numbers back into the module entry they were read from, leaving its scale and
|
every model that has moved, not only the one in front of you - a row that has
|
||||||
its origin alone. Placement lives in the module, so a model no module names -
|
been moved and not yet written is marked with a `*`, so what is pending is on
|
||||||
a sword part, an icon of its own - has nowhere to write to and says so. When it
|
the list rather than in your memory - putting each model's numbers back into
|
||||||
is the geometry itself that wants moving, ARMOUR_GUI edits the boxes and
|
the module entry they were read from, and leaving its scale and its origin
|
||||||
ARMOUR_EDITOR poses the result in a scene.
|
alone. Placement lives in the module, so a model no module names - a sword
|
||||||
|
part, an icon of its own - has nowhere to write to and is named in the report
|
||||||
|
instead. When it is the geometry itself that wants moving, ARMOUR_GUI edits the
|
||||||
|
boxes and ARMOUR_EDITOR poses the result in a scene.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -384,6 +387,7 @@ class Quickstart(QtWidgets.QMainWindow):
|
||||||
entry.matrix = mc.transform_matrix(
|
entry.matrix = mc.transform_matrix(
|
||||||
{group: {axis: self.spins[(group, axis)].value() for axis in 'xyz'}
|
{group: {axis: self.spins[(group, axis)].value() for axis in 'xyz'}
|
||||||
for group in ('translation', 'rotation')})
|
for group in ('translation', 'rotation')})
|
||||||
|
self._mark_moved()
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
def reset_transform(self):
|
def reset_transform(self):
|
||||||
|
|
@ -393,50 +397,65 @@ class Quickstart(QtWidgets.QMainWindow):
|
||||||
return
|
return
|
||||||
entry.matrix = entry.given.copy()
|
entry.matrix = entry.given.copy()
|
||||||
self._sync_transform()
|
self._sync_transform()
|
||||||
|
self._mark_moved()
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
|
def moved(self):
|
||||||
|
"""Every model whose transform is no longer what its module declares."""
|
||||||
|
return [e for e in self.entries if not np.allclose(e.matrix, e.given)]
|
||||||
|
|
||||||
def write_to_source(self):
|
def write_to_source(self):
|
||||||
"""Put the panel's numbers back into the module that declares the model.
|
"""Put the moved models' numbers back into the modules that declare them.
|
||||||
|
|
||||||
Placement lives in the module rather than in the geometry, so that is
|
Everything that has moved, not just the row in front of you: a session
|
||||||
what is written: the `translation` and `rotation` of the model entry
|
is spent nudging several things into place, and a button that saved one
|
||||||
the tool read this placement from, with its scale and its origin left
|
of them would leave the rest to be noticed later or lost. Placement
|
||||||
as the module had them. A model no module names has nowhere to write
|
lives in the module, so what is written is the `translation` and the
|
||||||
to - its geometry is its position - and says so rather than guessing.
|
`rotation` of the model entry each placement was read from, with its
|
||||||
|
scale and its origin left as the module had them.
|
||||||
|
|
||||||
This is the one thing here that changes a file.
|
A model no module names has nowhere to write to - its geometry is its
|
||||||
|
position - and is named in the report rather than passed over.
|
||||||
|
|
||||||
|
This is the one thing here that changes a module file.
|
||||||
"""
|
"""
|
||||||
entry = self._current()
|
moved = self.moved()
|
||||||
if entry is None:
|
if not moved:
|
||||||
return
|
self.statusBar().showMessage('nothing has moved - nothing to write')
|
||||||
if entry.source is None or not entry.loose:
|
|
||||||
self.statusBar().showMessage(
|
|
||||||
f'{entry.label}: no module in this tree names it, so there is no'
|
|
||||||
' transform to write - move the boxes in ARMOUR_GUI instead')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
path, section, index = entry.source
|
written, refused = [], []
|
||||||
full = self._on_disk(path)
|
for entry in moved:
|
||||||
if full is None:
|
if entry.source is None or not entry.loose:
|
||||||
self.statusBar().showMessage(f'{path}: not in a directory to write to')
|
refused.append(entry.label)
|
||||||
return
|
continue
|
||||||
with open(full) as fh:
|
path, section, index = entry.source
|
||||||
doc = json.load(fh)
|
full = self._on_disk(path)
|
||||||
model = doc['data'][section]['model']
|
if full is None:
|
||||||
target = (model[index] if isinstance(model, list) else model)
|
refused.append(entry.label)
|
||||||
transform = target.setdefault('transform', {})
|
continue
|
||||||
written = mc.decompose(entry.matrix)
|
with open(full) as fh:
|
||||||
for group in ('translation', 'rotation'):
|
doc = json.load(fh)
|
||||||
transform[group] = {a: _round(written[group][a]) for a in 'xyz'}
|
model = doc['data'][section]['model']
|
||||||
with open(full, 'w') as fh:
|
target = (model[index] if isinstance(model, list) else model)
|
||||||
fh.write(ae._dumps(doc))
|
transform = target.setdefault('transform', {})
|
||||||
fh.write('\n')
|
numbers = mc.decompose(entry.matrix)
|
||||||
|
for group in ('translation', 'rotation'):
|
||||||
|
transform[group] = {a: _round(numbers[group][a]) for a in 'xyz'}
|
||||||
|
with open(full, 'w') as fh:
|
||||||
|
fh.write(ae._dumps(doc))
|
||||||
|
fh.write('\n')
|
||||||
|
# What the file says is what is declared now, so this is the state
|
||||||
|
# `Reset to declared` comes back to and the row stops being marked.
|
||||||
|
entry.given = entry.matrix.copy()
|
||||||
|
entry.declared = True
|
||||||
|
written.append(os.path.basename(path))
|
||||||
|
|
||||||
# What the file says is what is declared now, so this is the state
|
self._mark_moved()
|
||||||
# `Reset to declared` should come back to.
|
said = f'wrote {len(written)}: ' + ', '.join(written) if written else ''
|
||||||
entry.given = entry.matrix.copy()
|
if refused:
|
||||||
entry.declared = True
|
said += ('; ' if said else '') + 'no module names ' + ', '.join(refused)
|
||||||
self.statusBar().showMessage(f'wrote the transform to {path}')
|
self.statusBar().showMessage(said)
|
||||||
|
|
||||||
def unwrap_uvs(self, _checked=False):
|
def unwrap_uvs(self, _checked=False):
|
||||||
"""Re-cut the selected model's texture so every face has its own patch.
|
"""Re-cut the selected model's texture so every face has its own patch.
|
||||||
|
|
@ -575,6 +594,7 @@ class Quickstart(QtWidgets.QMainWindow):
|
||||||
for actor in self._actors_of(entry):
|
for actor in self._actors_of(entry):
|
||||||
actor.user_matrix = np.eye(4)
|
actor.user_matrix = np.eye(4)
|
||||||
self._sync_transform()
|
self._sync_transform()
|
||||||
|
self._mark_moved()
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
def _folded(self, entry, user_matrix):
|
def _folded(self, entry, user_matrix):
|
||||||
|
|
@ -679,6 +699,20 @@ class Quickstart(QtWidgets.QMainWindow):
|
||||||
def _count(self):
|
def _count(self):
|
||||||
self.count_label.setText(f'{len(self.entries)} armour models')
|
self.count_label.setText(f'{len(self.entries)} armour models')
|
||||||
|
|
||||||
|
def _mark_moved(self):
|
||||||
|
"""Mark the rows whose model has been moved but not yet written.
|
||||||
|
|
||||||
|
Editing the text is what makes a row say it; doing that quietly is
|
||||||
|
what keeps it from being read back as a tick and redrawing the scene
|
||||||
|
on every drag.
|
||||||
|
"""
|
||||||
|
moved = {id(e) for e in self.moved()}
|
||||||
|
self.list.blockSignals(True)
|
||||||
|
for item in self._rows():
|
||||||
|
entry = self.entries[item.data(QtCore.Qt.UserRole)]
|
||||||
|
item.setText(entry.label + (' *' if id(entry) in moved else ''))
|
||||||
|
self.list.blockSignals(False)
|
||||||
|
|
||||||
def _rows(self):
|
def _rows(self):
|
||||||
for i in range(self.list.count()):
|
for i in range(self.list.count()):
|
||||||
yield self.list.item(i)
|
yield self.list.item(i)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue