# -*- coding: utf-8 -*-
# plot_utils.py
# Common plotting utilities for EiBotBoard
# https://github.com/evil-mad/plotink
#
# Intended to provide some common interfaces that can be used by
# EggBot, WaterColorBot, AxiDraw, and similar machines.
#
# See below for version information
#
#
# The MIT License (MIT)
#
# Copyright (c) 2019 Windell H. Oskay, Evil Mad Scientist Laboratories
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from math import sqrt
import cspsubdiv
import simplepath
import bezmisc
import ffgeom
def version(): # Version number for this document
return "0.16" # Dated 2019-06-18
__version__ = version()
PX_PER_INCH = 96.0
# This value has changed to 96 px per inch, as of version 0.12 of this library.
# Prior versions used 90 PPI, corresponding the value used in Inkscape < 0.92.
# For use with Inkscape 0.91 (or older), use PX_PER_INCH = 90.0
trivial_svg = """
"""
def checkLimits(value, lower_bound, upper_bound):
# Limit a value to within a range.
# Return constrained value with error boolean.
if value > upper_bound:
return upper_bound, True
if value < lower_bound:
return lower_bound, True
return value, False
def checkLimitsTol(value, lower_bound, upper_bound, tolerance):
# Limit a value to within a range.
# Return constrained value with error boolean.
# Allow a range of tolerance where we constrain the value without an error message.
if value > upper_bound:
if value > (upper_bound + tolerance):
return upper_bound, True # Truncate & throw error
else:
return upper_bound, False # Truncate with no error
if value < lower_bound:
if value < (lower_bound - tolerance):
return lower_bound, True # Truncate & throw error
else:
return lower_bound, False # Truncate with no error
return value, False # Return original value without error
def clip_code(x, y, x_min, x_max, y_min, y_max):
# Encode point position with respect to boundary box
code = 0
if x < x_min:
code = 1 # Left
if x > x_max:
code |= 2 # Right
if y < y_min:
code |= 4 # Top
if y > y_max:
code |= 8 # Bottom
return code
def clip_segment(segment, bounds):
"""
Given an input line segment [[x1,y1],[x2,y2]], as well as a
rectangular bounding region [[x_min,y_min],[x_max,y_max]], clip and
keep the part of the segment within the bounding region, using the
Cohen–Sutherland algorithm.
Return a boolean value, "accept", indicating that the output
segment is non-empty, as well as truncated segment,
[[x1',y1'],[x2',y2']], giving the portion of the input line segment
that fits within the bounds.
"""
x1 = segment[0][0]
y1 = segment[0][1]
x2 = segment[1][0]
y2 = segment[1][1]
x_min = bounds[0][0]
y_min = bounds[0][1]
x_max = bounds[1][0]
y_max = bounds[1][1]
while True: # Repeat until return
code_1 = clip_code(x1, y1, x_min, x_max, y_min, y_max)
code_2 = clip_code(x2, y2, x_min, x_max, y_min, y_max)
# Trivial accept:
if code_1 == 0 and code_2 == 0:
return True, segment # Both endpoints are within bounds.
# Trivial reject, if both endpoints are outside, and on the same side:
if code_1 & code_2:
return False, segment # Verify with bitwise AND.
# Otherwise, at least one point is out of bounds; not trivial.
if code_1 != 0:
code = code_1
else:
code = code_2
# Clip at a single boundary; may need to do this up to twice per vertex
if code & 1: # Vertex on LEFT side of bounds:
x = x_min # Find intersection of our segment with x_min
slope = (y2 - y1) / (x2 - x1)
y = slope * (x_min - x1) + y1
elif code & 2: # Vertex on RIGHT side of bounds:
x = x_max # Find intersection of our segment with x_max
slope = (y2 - y1) / (x2 - x1)
y = slope * (x_max - x1) + y1
elif code & 4: # Vertex on TOP side of bounds:
y = y_min # Find intersection of our segment with y_min
slope = (x2 - x1) / (y2 - y1)
x = slope * (y_min - y1) + x1
elif code & 8: # Vertex on BOTTOM side of bounds:
y = y_max # Find intersection of our segment with y_max
slope = (x2 - x1) / (y2 - y1)
x = slope * (y_max - y1) + x1
if code == code_1:
x1 = x
y1 = y
else:
x2 = x
y2 = y
segment = [[x1,y1],[x2,y2]] # Now checking this clipped segment
def constrainLimits(value, lower_bound, upper_bound):
# Limit a value to within a range.
return max(lower_bound, min(upper_bound, value))
def distance(x, y):
"""
Pythagorean theorem
"""
return sqrt(x * x + y * y)
def dotProductXY(input_vector_first, input_vector_second):
temp = input_vector_first[0] * input_vector_second[0] + input_vector_first[1] * input_vector_second[1]
if temp > 1:
return 1
elif temp < -1:
return -1
else:
return temp
def getLength(altself, name, default):
"""
Get the