summaryrefslogtreecommitdiffstats
path: root/share/extensions/measure.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/measure.py
parentInitial commit. (diff)
downloadinkscape-upstream.tar.xz
inkscape-upstream.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'share/extensions/measure.py')
-rwxr-xr-xshare/extensions/measure.py286
1 files changed, 286 insertions, 0 deletions
diff --git a/share/extensions/measure.py b/share/extensions/measure.py
new file mode 100755
index 0000000..2e9d8f5
--- /dev/null
+++ b/share/extensions/measure.py
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+# coding=utf-8
+#
+# Copyright (C) 2015 ~suv <suv-sf@users.sf.net>
+# Copyright (C) 2010 Alvin Penner
+# Copyright (C) 2006 Georg Wiora
+# Copyright (C) 2006 Nathan Hurst
+# Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+"""
+This extension module can measure arbitrary path and object length
+It adds text to the selected path containing the length in a given unit.
+Area and Center of Mass calculated using Green's Theorem:
+http://mathworld.wolfram.com/GreensTheorem.html
+"""
+
+import inkex
+
+from inkex import TextElement, TextPath, Tspan
+from inkex.bezier import csparea, cspcofm, csplength
+from inkex.localization import inkex_gettext as _
+
+
+class MeasureLength(inkex.EffectExtension):
+ """Measure the length of selected paths"""
+
+ def add_arguments(self, pars):
+ pars.add_argument(
+ "--type", dest="mtype", default="length", help="Type of measurement"
+ )
+ pars.add_argument(
+ "--method",
+ type=self.arg_method(),
+ default=self.method_textonpath,
+ help="Text Orientation method",
+ )
+ pars.add_argument(
+ "--presetFormat", default="default", help="Preset text layout"
+ )
+ pars.add_argument(
+ "--startOffset", default="custom", help="Text Offset along Path"
+ )
+ pars.add_argument(
+ "--startOffsetCustom", type=int, default=50, help="Text Offset along Path"
+ )
+ pars.add_argument("--anchor", default="start", help="Text Anchor")
+ pars.add_argument("--position", default="start", help="Text Position")
+ pars.add_argument("--angle", type=float, default=0, help="Angle")
+ pars.add_argument(
+ "-f",
+ "--fontsize",
+ type=int,
+ default=12,
+ help="Size of length label text in px",
+ )
+ pars.add_argument(
+ "-o",
+ "--offset",
+ type=float,
+ default=-6,
+ help="The distance above the curve",
+ )
+ pars.add_argument(
+ "-u", "--unit", default="px", help="The unit of the measurement"
+ )
+ pars.add_argument(
+ "-p",
+ "--precision",
+ type=int,
+ default=2,
+ help="Number of significant digits after decimal point",
+ )
+ pars.add_argument(
+ "-s",
+ "--scale",
+ type=float,
+ default=1.0,
+ help="Scale Factor (Drawing:Real Length)",
+ )
+
+ def effect(self):
+ # get number of digits
+ prec = int(self.options.precision)
+ scale = self.svg.viewport_to_unit(
+ "1" + self.svg.document_unit
+ ) # convert to document units
+ self.options.offset *= scale
+
+ factor = self.svg.unit_to_viewport(1, self.options.unit)
+
+ # loop over all selected paths
+ filtered = self.svg.selection.filter(inkex.PathElement)
+ if not filtered:
+ raise inkex.AbortExtension(_("Please select at least one path object."))
+ for node in filtered:
+ csp = node.path.transform(node.composed_transform()).to_superpath()
+ inverse_parent_transform = -node.getparent().composed_transform()
+ if self.options.mtype == "length":
+ slengths, stotal = csplength(csp)
+ self.group = node.getparent().add(TextElement())
+ elif self.options.mtype == "area":
+ stotal = abs(csparea(csp) * factor * self.options.scale)
+ self.group = node.getparent().add(TextElement())
+ else:
+ try:
+ xc, yc = cspcofm(csp)
+ except ValueError as err:
+ raise inkex.AbortExtension(str(err))
+ self.group = node.getparent().add(inkex.PathElement())
+ self.group.set("id", "MassCenter_" + node.get("id"))
+ self.add_cross(self.group, xc, yc, scale)
+ self.group.transform = inverse_parent_transform
+ continue
+ # Format the length as string
+ val = round(stotal * factor * self.options.scale, prec)
+ # Transform the result back
+ self.options.method(node, str(val))
+
+ def method_textonpath(self, node, lenstr):
+ startOffset = self.options.startOffset
+ if startOffset == "custom":
+ startOffset = str(self.options.startOffsetCustom) + "%"
+ if self.options.mtype == "length":
+ self.add_textonpath(
+ self.group,
+ 0,
+ 0,
+ lenstr + " " + self.options.unit,
+ node,
+ self.options.anchor,
+ startOffset,
+ self.options.offset,
+ )
+ else:
+ self.add_textonpath(
+ self.group,
+ 0,
+ 0,
+ lenstr + " " + self.options.unit + "^2",
+ node,
+ self.options.anchor,
+ startOffset,
+ self.options.offset,
+ )
+
+ def method_fixedtext(self, node, lenstr):
+ _id = node.get("id")
+ csp = node.path.transform(node.composed_transform()).to_superpath()
+ if self.options.position == "mass":
+ tx, ty = cspcofm(csp)
+ anchor = "middle"
+ elif self.options.position == "center":
+ bbox = node.bounding_box(True)
+ tx, ty = bbox.center
+ anchor = "middle"
+ else: # default
+ tx = csp[0][0][1][0]
+ ty = csp[0][0][1][1]
+ anchor = "start"
+ if self.options.mtype == "length":
+ self.add_fixedtext(
+ self.group,
+ tx,
+ ty,
+ lenstr + " " + self.options.unit,
+ anchor,
+ -int(self.options.angle),
+ self.options.offset + self.options.fontsize / 2,
+ )
+ else:
+ self.add_fixedtext(
+ self.group,
+ tx,
+ ty,
+ lenstr + " " + self.options.unit + "^2",
+ anchor,
+ -int(self.options.angle),
+ -self.options.offset + self.options.fontsize / 2,
+ )
+
+ def method_presets(self, node, lenstr):
+ """A preset option for alignments"""
+ preset_dict = {
+ "default_cofm": [None, None, None, None, None],
+ "default_length": [self.method_textonpath, "50%", "start", None, None],
+ "TaP_start": [self.method_textonpath, "0%", "start", None, None],
+ "TaP_middle": [self.method_textonpath, "50%", "middle", None, None],
+ "TaP_end": [self.method_textonpath, "100%", "end", None, None],
+ "default_area": [self.method_fixedtext, None, None, "start", 0.0],
+ "FT_start": [self.method_fixedtext, None, None, "start", 0.0],
+ "FT_bbox": [self.method_fixedtext, None, None, "center", 0.0],
+ "FT_mass": [self.method_fixedtext, None, None, "mass", 0.0],
+ }
+
+ if self.options.presetFormat == "default":
+ current_preset = "default_" + self.options.mtype
+ else:
+ current_preset = self.options.presetFormat
+
+ self.options.startOffset = preset_dict[current_preset][1]
+ self.options.anchor = preset_dict[current_preset][2]
+ self.options.position = preset_dict[current_preset][3]
+ self.options.angle = preset_dict[current_preset][4]
+ method = preset_dict[current_preset][0]
+ if method is not None:
+ return method(node, lenstr)
+
+ def add_cross(self, node, x, y, scale):
+ l = 3 * scale # 3 pixels in document units
+ node.set(
+ "d",
+ "m %s,%s %s,0 %s,0 m %s,%s 0,%s 0,%s"
+ % (str(x - l), str(y), str(l), str(l), str(-l), str(-l), str(l), str(l)),
+ )
+ node.set("style", "stroke:#000000;fill:none;stroke-width:%s" % str(0.5 * scale))
+
+ def add_textonpath(self, node, x, y, text, _node, anchor, startOffset, dy=0):
+ new = node.add(TextPath())
+ s = {
+ "text-align": "center",
+ "vertical-align": "bottom",
+ "text-anchor": anchor,
+ "font-size": str(self.options.fontsize),
+ "fill-opacity": "1.0",
+ "stroke": "none",
+ "font-weight": "normal",
+ "font-style": "normal",
+ "fill": "#000000",
+ }
+ new.style = s
+ new.href = _node
+ new.set("startOffset", startOffset)
+ new.set("dy", str(dy)) # dubious merit
+ # new.append(tp)
+ if text[-2:] == "^2":
+ new.append(Tspan.superscript("2"))
+ new.text = str(text)[:-2]
+ else:
+ new.text = str(text)
+ # node.set('transform','rotate(180,'+str(-x)+','+str(-y)+')')
+ node.set("x", str(x))
+ node.set("y", str(y))
+
+ def add_fixedtext(self, node, x, y, text, anchor, angle, dy=0):
+ new = node.add(Tspan())
+ new.set("sodipodi:role", "line")
+ s = {
+ "text-align": "center",
+ "vertical-align": "bottom",
+ "text-anchor": anchor,
+ "font-size": self.svg.viewport_to_unit(self.options.fontsize),
+ "fill-opacity": "1.0",
+ "stroke": "none",
+ "font-weight": "normal",
+ "font-style": "normal",
+ "fill": "#000000",
+ }
+ new.style = s
+ new.set("dy", str(dy))
+ if text[-2:] == "^2":
+ new.append(Tspan.superscript("2"))
+ new.text = str(text)[:-2]
+ else:
+ new.text = str(text)
+ node.set("x", str(x))
+ node.set("y", str(y))
+ node.set("transform", "rotate(%s, %s, %s)" % (angle, x, y))
+ node.transform = -node.getparent().composed_transform() @ node.transform
+
+
+if __name__ == "__main__":
+ MeasureLength().run()