-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unexpected order of operations when combining flip and offset #31
Comments
@tyehle Any opinion on this? |
@jwlarocque Thanks for the feedback. IIRC, we had some discussion on ordering already. Moving offset after |
This seems accurate. I would expect the translate to have an absolute rather than a flipped effect. If we apply it the way we do it's Though really we might need a better arranged solution altogether since you might want some other things. And frankly this might be better off as part of vpype more properly. Something like CSS matrix parsing would make sense with a couple extra operations. Changing the order of the static operations would always change how it works. Unless for some reason it multiplied by 0 or something, or otherwise couldn't matter. But flips, inverts, and offsets are equal to |
I like this idea of explicitly listing transforms in order! This is how it could look like in the toml config file: [gwrite.demo]
# ...
# scale_x = 10 # dont use, depreciated, still works as before
# offset_x = ... # same
# flip_x = true # same
transforms = [
{ type = "scale_x", value = 10 },
{ type = "flip_y", value = true },
{ type = "offset_x", value = "3cm" },
] The operations would of course be applied in the listed order. The old parameters would remain and keep their effect, but be deprecated (non 0 value yield warning, removed altogether after a while). |
Seems like flip_y wouldn't really need a value. And that formatting feels a bit hamstrung. [gwrite.demo]
# ...
# scale_x = 10 # dont use, depreciated, still works as before
# offset_x = ... # same
# flip_x = true # same
transforms = [
"scale_x(10)",
"flip_y",
"offset_x(3cm)",
] or, more straightforward: [gwrite.demo]
# ...
# scale_x = 10 # dont use, depreciated, still works as before
# offset_x = ... # same
# flip_x = true # same
transform = "scale_x(10) flip_y offset_x(3cm)" It seems like you could even just inline that as a quoted things See: https://github.com/meerk40t/svgelements/blob/fcf3bdd8997ca48b19fa63487cef42fa0d7d12d1/svgelements/svgelements.py#L2649 for the basic parsing routine. |
I like the readability and conciseness of your first proposal (TOML array but SVG-like transform syntax). It somehow strikes a nice balance between self-explanatory, not too hacky in the context of a TOML config file, trivial parsing code (basically a regexp), and not too verbose (like my initial proposal). IMHO, it wouldn't make sense to force this into direct compatibility with svgelements. The loss in extensibility/readability isn't worth the implementation gains. |
I was noting that parsing there is usable prior art, not that we should take it. There is no function in CSS matrix that allows flipping a document which is largely what you we want here. Also, a quick parser for the relevant transforms could probably do either several inline operations or singletons as you could send In vpype-gcode we're calling commands like: document.scale(scale_x / unit_scale, scale_y / unit_scale)
document.translate(offset_x, offset_y)
document.translate(-origin[0], -origin[1])
...
document.scale(-1 if invert_x else 1, -1 if invert_y else 1)
document.translate(origin[0], origin[1]) class DummyDocument:
def __getattribute__(self, item):
print(item)
return print
import re
parse = "translate(2in, 3in) flip_x flip_y rotate(3deg) skew(1.2, 1.8)"
transform_re = re.compile(r"(?u)(scale|translate|rotate|skew|flip_x|flip_y)[\s\t\n]*(?:\(([^)]+)\))?")
param_re = re.compile(r"([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)[\s\t\n]*(cm|mm|in|deg)?")
document = DummyDocument()
for m in transform_re.findall(parse):
name = m[0]
params = [(float(mag), unit, mag + unit) for mag, unit in param_re.findall(m[1])]
if name == "scale":
assert(len(params) == 2)
document.scale(params[0][0], params[1][0])
elif name == "translate":
assert(len(params) == 2)
document.translate(params[0][2], params[1][2])
elif name == "rotate":
assert (len(params) == 1)
document.rotate(params[0][0])
elif name == "skew":
assert (len(params) == 2)
document.skew(params[0][0], params[1][0])
elif name == "flip_x":
assert (len(params) == 0)
document.flip_x()
elif name == "flip_y":
assert (len(params) == 0)
document.flip_x() Is a basic parser. I think things like document.translate() can take "2in", "3in" sorts of things. Given the dummy document this output:
Obviously it could take whatever units the documents take for those operations, or just hand it floats if needed. But, in theory that would work. Then we leave the rest of the stuff untouched, deprecate the forced position With the addition of percent as a length (equal to percent of document size) it would be possible to single-line the flip_x flip_y stuff. Namely you'd allow the command You could add a document.matrix() function, though we could probably just issue them one at a time in sequence. Though it would imply that rather than def transform(self, mx):
for line in self._lines:
line.real = line.real * mx.a + line.imag * mx.c + 1 * mx.e
line.imag = line.real * mx.b + line.imag * mx.d + 1 * mx.f Assuming the matrix bits were a-f, when really a numpy array would also tend to work there. We'd do this for speed, generally it doesn't come up that much but all the affine transformations can be applied within the matrix and then we only multiply the giant set of numbers the one time. But, I'd assume we'd stick to regular parsing, and stick to only modifying vpype-gcode. That little bit of code is pretty easy to setup. |
Great input thanks–we could definitely do something like this. Now, to shake things up, here is a completely different approach that just occurred to me: use actual vpype commands instead this new DSL of sort. In the TOML: [gwrite.demo]
# ...
transform = "translate 0 10cm scale -o 0 %vp_page_width.h/2% 1 -1 scale 0.5 0.5" (The param would probably be better named This would be actual vpype commands, so the implementation becomes a one-liner: doc = vpype_cli.execute(preprocess_commands, doc) This would be nice because (1) it's trivial to implement, (2) builds on existing user knowledge instead of introducing yet another DSL, (3) easily extensible by construction. I see at least these issues (there may be more):
|
I think that's a winner. If you don't call That does seem pretty simple and effective. Even the parsing section, I realized things like reloop could be trivially added. And well, most of that stuff is or should be in the vpype pypeline anyway. We make a copy of the document during saving so we're not modifying it directly. Even the parsing stuff feels like, if it was implemented, it should be in vpype directly. Your default unit is You'd need to make Also, since the goal of this project more or less is to make the project not exist, more utility moving from This gets a little weirder since invoking gwrite with a profile that preprocess a |
I noticed that when using the flip options, offsets are applied before the flip. In my opinion this is unintuitive and I would propose either changing it (since the feature is relatively new, this hopefully wouldn't break too many workflows) or documenting the behavior in the readme.
Relevant code:
vpype-gcode/vpype_gcode/gwrite.py
Lines 133 to 145 in 1a74dec
Example of current behavior (gcode)
SVG 175mm tall, with a line segment in the top left: ((10, 10), (30, 10))
profile toml:
When
vertical_flip = false
, I get this gcode, as expected:When
vertical_flip = true
, I would expect:but instead I get:
(The expected result can be achieved with
offset_y = -45
.)The text was updated successfully, but these errors were encountered: