add file and dir with name changed

parent 2edfc141
../share/mime/pycam.mime
\ No newline at end of file
../share/mime/pycam.xml
\ No newline at end of file
[EXAMPLES]
.nf
.B pymkcam \-\-export\-gcode=output.ngc \-\-bounds\-type=relative\-margin \-\-bounds-lower=0.1,0.05,-0.1 foo.stl
.fi
Use the default settings to process the model \fBfoo.stl\fR with an adjusted
lower margin (minx, miny, minz) of 10% (for x), 5% (for y) and \-10% (for z).
[ENVIRONMENT]
.IP PYCAM_DATA_DIR
Override the default data directory of pyMKcam. This allows
you to provide customized logos, menu files or non-default sample files.
.IP PYCAM_FONT_DIR
Override the default location of engrave fonts.
.IP PYTHONPATH
You may want to define this variable in case that you installed the
\fBpyMKcam\fR python package in a non-default location.
[REPORTING BUGS]
See http://sourceforge.net/tracker/?group_id=237831&atid=1104176
[SEE ALSO]
Take a look at the output of \fBpymkcam \-\-help\fR to get a slightly better
formatted list of options. The manual that you are reading right now is
derived from this output.
Take a look at the wiki for more information about pyMKcam:
http://sourceforge.net/apps/mediawiki/pymkcam/
The website of the pyMKcam project: http://pymkcam.sourceforge.net
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2008-2010 Lode Leroy
Copyright 2010-2011 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam.Geometry import IDGenerator
from pymkcam.Geometry.PointUtils import *
from pymkcam.Geometry.utils import number, INFINITE, epsilon
from pymkcam.Geometry.intersection import intersect_cylinder_point, \
intersect_cylinder_line
import uuid
class BaseCutter(IDGenerator):
vertical = (0, 0, -1)
def __init__(self, radius, location=None, height=None):
super(BaseCutter, self).__init__()
if location is None:
location = (0, 0, 0)
if height is None:
height = 10
radius = number(radius)
self.height = number(height)
self.radius = radius
self.radiussq = radius ** 2
self.required_distance = 0
self.distance_radius = self.radius
self.distance_radiussq = self.distance_radius ** 2
self.shape = {}
self.location = location
self.moveto(self.location)
self.uuid = None
self.update_uuid()
def get_minx(self, start=None):
if start is None:
start = self.location
return start[0] - self.distance_radius
def get_maxx(self, start=None):
if start is None:
start = self.location
return start[0] + self.distance_radius
def get_miny(self, start=None):
if start is None:
start = self.location
return start[1] - self.distance_radius
def get_maxy(self, start=None):
if start is None:
start = self.location
return start[1] + self.distance_radius
def update_uuid(self):
self.uuid = uuid.uuid4()
def __repr__(self):
return "BaseCutter"
def __cmp__(self, other):
""" Compare Cutters by shape and size (ignoring the location)
This function should be overridden by subclasses, if they describe
cutters with a shape depending on more than just the radius.
See the ToroidalCutter for an example.
"""
if self.__class__ == other.__class__:
return cmp(self.radius, other.radius)
else:
# just return a string comparison
return cmp(str(self), str(other))
def set_required_distance(self, value):
if value >= 0:
self.required_distance = number(value)
self.distance_radius = self.radius + self.get_required_distance()
self.distance_radiussq = self.distance_radius * self.distance_radius
self.update_uuid()
def get_required_distance(self):
return self.required_distance
def moveto(self, location):
# "moveto" is used for collision detection calculation.
self.location = location
for shape, set_pos_func in self.shape.values():
set_pos_func(location[0], location[1], location[2])
def intersect(self, direction, triangle, start=None):
raise NotImplementedError("Inherited class of BaseCutter does not " \
+ "implement the required function 'intersect'.")
def drop(self, triangle, start=None):
if start is None:
start = self.location
# check bounding box collision
if self.get_minx(start) > triangle.maxx + epsilon:
return None
if self.get_maxx(start) < triangle.minx - epsilon:
return None
if self.get_miny(start) > triangle.maxy + epsilon:
return None
if self.get_maxy(start) < triangle.miny - epsilon:
return None
# check bounding circle collision
c = triangle.middle
if (c[0] - start[0]) ** 2 + (c[1] - start[1]) ** 2 \
> (self.distance_radiussq + 2 * self.distance_radius \
* triangle.radius + triangle.radiussq) + epsilon:
return None
return self.intersect(BaseCutter.vertical, triangle, start=start)[0]
def intersect_circle_triangle(self, direction, triangle, start=None):
(cl, ccp, cp, d) = self.intersect_circle_plane(direction, triangle,
start=start)
if cp and triangle.is_point_inside(cp):
return (cl, d, cp)
return (None, INFINITE, None)
def intersect_circle_vertex(self, direction, point, start=None):
(cl, ccp, cp, l) = self.intersect_circle_point(direction, point,
start=start)
return (cl, l, cp)
def intersect_circle_edge(self, direction, edge, start=None):
(cl, ccp, cp, l) = self.intersect_circle_line(direction, edge,
start=start)
if cp:
# check if the contact point is between the endpoints
m = pdot(psub(cp, edge.p1), edge.dir)
if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, cp)
return (cl, l, cp)
def intersect_cylinder_point(self, direction, point, start=None):
if start is None:
start = self.location
(ccp, cp, l) = intersect_cylinder_point(
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, point)
# offset intersection
if ccp:
cl = padd(start, psub(cp, ccp))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
def intersect_cylinder_vertex(self, direction, point, start=None):
if start is None:
start = self.location
(cl, ccp, cp, l) = self.intersect_cylinder_point(direction, point,
start=start)
if ccp and ccp[2] < padd(psub(start, self.location), self.center)[2]:
return (None, INFINITE, None)
return (cl, l, cp)
def intersect_cylinder_line(self, direction, edge, start=None):
if start is None:
start = self.location
(ccp, cp, l) = intersect_cylinder_line(
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, edge)
# offset intersection
if ccp:
cl = padd(start, psub(cp, ccp))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
def intersect_cylinder_edge(self, direction, edge, start=None):
if start is None:
start = self.location
(cl, ccp, cp, l) = self.intersect_cylinder_line(direction, edge,
start=start)
if not ccp:
return (None, INFINITE, None)
m = pdot(psub(cp, edge.p1), edge.dir)
if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, None)
if ccp[2] < padd(psub(start, self.location), self.center)[2]:
return (None, INFINITE, None)
return (cl, l, cp)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
__all__ = [ "SphericalCutter", "CylindricalCutter", "ToroidalCutter",
"BaseCutter" ]
from pymkcam.Cutters.BaseCutter import BaseCutter
from pymkcam.Cutters.SphericalCutter import SphericalCutter
from pymkcam.Cutters.CylindricalCutter import CylindricalCutter
from pymkcam.Cutters.ToroidalCutter import ToroidalCutter
def get_tool_from_settings(tool_settings, height=None):
""" get the tool specified by the relevant settings
The settings must include:
- "shape": one of "SphericalCutter", "CylindricalCutter" and
"ToroidalCutter"
- "radius": the tool radius
The following settings are optional or shape specific:
- "torus_radius": necessary for ToroidalCutter
@type tool_settings: dict
@value tool_settings: contains the attributes of the tool
@type height: float
@value height: the height of the tool
@rtype: BaseCutter | basestring
@return: a tool object or an error string
"""
cuttername = tool_settings["shape"]
radius = tool_settings["tool_radius"]
if cuttername == "SphericalCutter":
return SphericalCutter(radius, height=height)
elif cuttername == "CylindricalCutter":
return CylindricalCutter(radius, height=height)
elif cuttername == "ToroidalCutter":
toroid = tool_settings["torus_radius"]
return ToroidalCutter(radius, toroid, height=height)
else:
return "Invalid cutter shape: '%s' is not known" % str(cuttername)
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM 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 3 of the License, or
(at your option) any later version.
PyCAM 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 PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import os
class EMCToolExporter(object):
def __init__(self, tools):
""" tools are expected to be dictionaries containing the following keys:
- id
- radius
- name
"""
self.tools = tools
def get_tool_definition_string(self):
result = []
tools = list(self.tools)
tools.sort(key=lambda item: item["id"])
#result.append(self.HEADER_ROW)
for tool in tools:
# use an arbitrary length
tool_length = tool["radius"] * 10
line = "T%d P%d D%f Z-%f ;%s" % (tool["id"], tool["id"],
2 * tool["radius"], tool_length, tool["name"])
result.append(line)
# add the dummy line for the "last" tool
result.append("T99999 P99999 Z+0.100000 ;dummy tool")
return os.linesep.join(result)
# -*- coding: utf-8 -*-
"""
Copyright 2012 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import pymkcam.Exporters.GCode
from pymkcam.Toolpath import CORNER_STYLE_EXACT_PATH, CORNER_STYLE_EXACT_STOP, \
CORNER_STYLE_OPTIMIZE_SPEED, CORNER_STYLE_OPTIMIZE_TOLERANCE
DEFAULT_HEADER = (("G40", "disable tool radius compensation"),
("G49", "disable tool length compensation"),
("G80", "cancel modal motion"),
("G54", "select coordinate system 1"),
("G90", "disable incremental moves"))
DEFAULT_DIGITS = 6
def _render_number(number):
if int(number) == number:
return "%d" % number
else:
return ("%%.%df" % DEFAULT_DIGITS) % number
class LinuxCNC(pymkcam.Exporters.GCode.BaseGenerator):
def add_header(self):
for command, comment in DEFAULT_HEADER:
self.add_command(command, comment=comment)
# TODO: use a "unit" filter
if True:
self.add_command("G21", "metric")
else:
self.add_command("G20", "imperial")
def add_footer(self):
self.add_command("M2", "end program")
def add_comment(self, comment):
self.add_command("; %s" % comment)
def add_command(self, command, comment=None):
self.destination.write(command)
if comment:
self.destination.write("\t")
self.add_comment(comment)
else:
self.destination.write(os.linesep)
def add_move(self, coordinates, is_rapid=False):
components = []
# the cached value may be:
# True: the last move was G0
# False: the last move was G1
# None: some non-move happened before
if self._get_cache("rapid_move", None) != is_rapid:
components.append("G0" if is_rapid else "G1")
else:
# improve gcode style
components.append(" ")
axes = [axis for axis in "XYZABCUVW"]
previous = self._get_cache("position", [None] * len(coordinates))
for (axis, value, last) in zip(axes, coordinates, previous):
if (last is None) or (last != value):
components.append("%s%.6f" % (axis, value))
command = " ".join(components)
if command.strip():
self.add_command(command)
def command_feedrate(self, feedrate):
self.add_command("F%s" % _render_number(feedrate), "set feedrate")
def command_select_tool(self, tool_id):
self.add_command("T%d M6" % tool_id, "select tool")
def command_spindle_speed(self, speed):
self.add_command("S%s" % _render_number(speed), "set spindle speed")
def command_spindle_enabled(self, state):
if state:
self.add_command("M3", "start spindle")
else:
self.add_command("M5", "stop spindle")
def command_delay(self, seconds):
self.add_command("G04 P%d" % seconds, "wait for %d seconds" % seconds)
def command_corner_style(self,
(path_mode, motion_tolerance, naive_tolerance)):
if path_mode == CORNER_STYLE_EXACT_PATH:
self.add_command("G61", "exact path mode")
elif path_mode == CORNER_STYLE_EXACT_STOP:
self.add_command("G61.1", "exact stop mode")
elif path_mode == CORNER_STYLE_OPTIMIZE_SPEED:
self.add_command("G64", "continuous mode with maximum speed")
else:
if not naive_tolerance:
self.add_command("G64 P%f" % motion_tolerance,
"continuous mode with tolerance")
else:
self.add_command("G64 P%f Q%f" % \
(motion_tolerance, naive_tolerance),
"continuous mode with tolerance and cleanup")
# -*- coding: utf-8 -*-
"""
Copyright 2012 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
import decimal
import os
import pymkcam.Utils.log
import pymkcam.Toolpath.Filters
from pymkcam.Toolpath import MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID, \
MACHINE_SETTING, COMMENT
_log = pymkcam.Utils.log.get_logger()
class BaseGenerator(object):
def __init__(self, destination):
if isinstance(destination, basestring):
# open the file
self.destination = file(destination,"w")
self._close_stream_on_exit = True
else:
# assume that "destination" is something like a StringIO instance
# or an open file
self.destination = destination
# don't close the stream if we did not open it on our own
self._close_stream_on_exit = False
self._filters = []
self._cache = {}
self.add_header()
def _get_cache(self, key, default_value):
return self._cache.get(key, default_value)
def add_filters(self, filters):
self._filters.extend(filters)
self._filters.sort()
def add_comment(self, comment):
raise NotImplementedError("someone forgot to implement 'add_comment'")
def add_command(self, command, comment=None):
raise NotImplementedError("someone forgot to implement 'add_command'")
def add_move(self, coordinates, is_rapid=False):
raise NotImplementedError("someone forgot to implement 'add_move'")
def add_footer(self):
raise NotImplementedError("someone forgot to implement 'add_footer'")
def finish(self):
self.add_footer()
if self._close_stream_on_exit:
self.destination.close()
def add_moves(self, moves, filters=None):
# combine both lists/tuples in a type-agnostic way
all_filters = list(self._filters)
if filters:
all_filters.extend(filters)
filtered_moves = pymkcam.Toolpath.Filters.get_filtered_moves(moves,
all_filters)
for move_type, args in filtered_moves:
if move_type in (MOVE_STRAIGHT, MOVE_STRAIGHT_RAPID):
is_rapid = move_type == MOVE_STRAIGHT_RAPID
self.add_move(args, is_rapid)
self._cache["position"] = args
self._cache["rapid_move"] = is_rapid
elif move_type == COMMENT:
self.add_comment(args)
elif move_type == MACHINE_SETTING:
key, value = args
func_name = "command_%s" % key
if hasattr(self, func_name):
_log.debug("GCode: machine setting '%s': %s" % (key, value))
getattr(self, func_name)(value)
self._cache[key] = value
self._cache["rapid_move"] = None
else:
_log.warn("The current GCode exporter does not support " + \
"the machine setting '%s=%s' -> ignore" % (key, value))
else:
_log.warn(("A non-basic toolpath item (%d -> %s) remained in the " + \
"queue -> ignore") % (move_type, args))
This diff is collapsed.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam import VERSION
from pymkcam.Geometry.PointUtils import pnormalized
import datetime
import os
class STLExporter(object):
def __init__(self, model, name="model", created_by="pymkcam", linesep=None,
**kwargs):
self.model = model
self.name = name
self.created_by = created_by
if linesep is None:
self.linesep = os.linesep
else:
self.linesep = linesep
def __str__(self):
return self.linesep.join(self.get_output_lines)
def write(self, stream):
for line in self.get_output_lines():
stream.write(line)
stream.write(self.linesep)
def get_output_lines(self):
date = datetime.date.today().isoformat()
yield """solid "%s"; Produced by %s (v%s), %s""" \
% (self.name, self.created_by, VERSION, date)
for triangle in self.model.triangles():
norm = pnormalized(triangle.normal)
yield "facet normal %f %f %f" % (norm[0], norm[1], norm[2])
yield " outer loop"
# Triangle vertices are stored in clockwise order - thus we need
# to reverse the order (STL expects counter-clockwise orientation).
for point in (triangle.p1, triangle.p3, triangle.p2):
yield " vertex %f %f %f" % (point[0], point[1], point[2])
yield " endloop"
yield "endfacet"
yield "endsolid"
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2009 Lode Leroy
This file is part of PyCAM.
PyCAM 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 3 of the License, or
(at your option) any later version.
PyCAM 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 PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
# Inkscape uses a fixed resolution of 90 dpi
SVG_OUTPUT_DPI = 90
class SVGExporter(object):
def __init__(self, output, unit="mm", maxx=None, maxy=None):
if isinstance(output, basestring):
# a filename was given
self.output = file(output,"w")
else:
# a stream was given
self.output = output
if unit == "mm":
dots_per_px = SVG_OUTPUT_DPI / 25.4
else:
dots_per_px = SVG_OUTPUT_DPI
if maxx is None:
width = 640
else:
width = dots_per_px * maxx
if width <= 0:
width = 640
if maxy is None:
height = 800
else:
height = dots_per_px * maxy
if height <= 0:
height = 800
self.output.write("""<?xml version='1.0'?>
<svg xmlns='http://www.w3.org/2000/svg' width='%f' height='%f'>
<g transform='translate(0,%f) scale(%.10f)' stroke-width='0.05' font-size='0.2'>
""" % (width, height, height, dots_per_px))
self._fill = 'none'
self._stroke = 'black'
def close(self, close_stream=True):
self.output.write("""</g>
</svg>
""")
if close_stream:
self.output.close()
def stroke(self, stroke):
self._stroke = stroke
def fill(self, fill):
self._fill = fill
def AddDot(self, x, y):
l = "<circle fill='" + self._fill +"'" + (" cx='%g'" % x) \
+ (" cy='%g'" % -y) + " r='0.04'/>\n"
self.output.write(l)
def AddText(self, x, y, text):
l = "<text fill='" + self._fill +"'" + (" x='%g'" % x) \
+ (" y='%g'" % -y) + " dx='0.07'>" + text + "</text>\n"
self.output.write(l)
def AddLine(self, x1, y1, x2, y2):
l = "<line fill='" + self._fill +"' stroke='" + self._stroke + "'" \
+ (" x1='%.8f'" % x1) + (" y1='%.8f'" % -y1) + (" x2='%.8f'" % x2) \
+ (" y2='%.8f'" % -y2) + " />\n"
self.output.write(l)
def AddPoint(self, p):
self.AddDot(p[0], p[1])
def AddPath(self, path):
self.AddLines(path.points)
def AddLines(self, points):
l = "<path fill='" + self._fill +"' stroke='" + self._stroke + "' d='"
for i in range(0, len(points)):
p = points[i]
if i == 0:
l += "M "
else:
l += " L "
l += "%.8f %.8f" % (p[0], -p[1])
l += "'/>\n"
self.output.write(l)
def AddPathList(self, pathlist):
for path in pathlist:
self.AddPath(path)
#TODO: we need to create a unified "Exporter" interface and base class
class SVGExporterContourModel(object):
def __init__(self, model, unit="mm", **kwargs):
self.model = model
self.unit = unit
def write(self, stream):
writer = SVGExporter(stream, unit=self.unit, maxx=self.model.maxx,
maxy=self.model.maxy)
for polygon in self.model.get_polygons():
points = polygon.get_points()
if polygon.is_closed:
points.append(points[0])
writer.AddLines(points)
writer.close(close_stream=False)
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
Copyright 2008-2009 Lode Leroy
This file is part of PyCAM.
PyCAM 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 3 of the License, or
(at your option) any later version.
PyCAM 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 PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
__all__ = [ "GCodeExporter", "SVGExporter", "STLExporter", "EMCToolExporter"]
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2008-2010 Lode Leroy
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam.Geometry import TransformableContainer
from pymkcam.Geometry.Model import ContourModel
from pymkcam.Geometry.Line import Line
from pymkcam.Geometry.PointUtils import *
TEXT_ALIGN_LEFT = 0
TEXT_ALIGN_CENTER = 1
TEXT_ALIGN_RIGHT = 2
class Letter(TransformableContainer):
def __init__(self, lines):
self.lines = lines
def minx(self):
return min([line.minx for line in self.lines])
def maxx(self):
return max([line.maxx for line in self.lines])
def miny(self):
return min([line.miny for line in self.lines])
def maxy(self):
return max([line.maxy for line in self.lines])
def get_positioned_lines(self, base_point, skew=None):
result = []
get_skewed_point = lambda p: (base_point[0] + p[0] + (p[1] * skew / 100.0), base_point[1] + p[1], base_point[2])
for line in self.lines:
skewed_p1 = get_skewed_point(line.p1)
skewed_p2 = get_skewed_point(line.p2)
# Some triplex fonts contain zero-length lines
# (e.g. "/" in italict.cxf). Ignore these.
if skewed_p1 != skewed_p2:
new_line = Line(skewed_p1, skewed_p2)
result.append(new_line)
return result
class Charset(object):
def __init__(self, name=None, author=None, letterspacing=3.0,
wordspacing=6.75, linespacingfactor=1.0, encoding=None):
self.letters = {}
self.letterspacing = letterspacing
self.wordspacing = wordspacing
self.linespacingfactor = linespacingfactor
self.default_linespacing = 1.6
self.default_height = 10.0
if name is None:
self.names = []
else:
if isinstance(name, (list, set, tuple)):
self.names = name
else:
self.names = [name]
if author is None:
self.authors = []
else:
if isinstance(author, (list, set, tuple)):
self.authors = author
else:
self.authors = [author]
if encoding is None:
self.encoding = "iso-8859-1"
else:
self.encoding = encoding
def add_character(self, character, lines):
if len(lines) > 0:
self.letters[character] = Letter(lines)
def get_names(self):
return self.names
def get_authors(self):
return self.authors
def render(self, text, origin=None, skew=0, line_spacing=1.0, pitch=1.0,
align=None):
result = ContourModel()
if origin is None:
origin = (0, 0, 0)
if align is None:
align = TEXT_ALIGN_LEFT
base = origin
letter_spacing = self.letterspacing * pitch
word_spacing = self.wordspacing * pitch
line_factor = self.default_linespacing * self.linespacingfactor \
* line_spacing
for line in text.splitlines():
current_line = ContourModel()
line_height = self.default_height
for character in line:
if character == " ":
base = padd(base, (word_spacing, 0, 0))
elif character in self.letters.keys():
charset_letter = self.letters[character]
new_model = ContourModel()
for line in charset_letter.get_positioned_lines(base,
skew=skew):
new_model.append(line, allow_reverse=True)
for polygon in new_model.get_polygons():
# add polygons instead of lines -> more efficient
current_line.append(polygon)
# update line height
line_height = max(line_height, charset_letter.maxy())
# shift the base position
base = padd(base, (charset_letter.maxx() + letter_spacing, 0, 0))
else:
# unknown character - add a small whitespace
base = padd(base, (letter_spacing, 0, 0))
# go to the next line
base = (origin[0], base[1] - line_height * line_factor, origin[2])
if not current_line.maxx is None:
if align == TEXT_ALIGN_CENTER:
current_line.shift(-current_line.maxx / 2, 0, 0)
elif align == TEXT_ALIGN_RIGHT:
current_line.shift(-current_line.maxx, 0, 0)
else:
# left align
if current_line.minx != 0:
current_line.shift(-current_line.minx, 0, 0)
for polygon in current_line.get_polygons():
result.append(polygon)
# the text should be just above the x axis
if result.miny:
# don't shift, if result.miny is None (e.g.: no content) or zero
result.shift(0, -result.miny, 0)
return result
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
# various matrix related functions for pyMKcam
from pymkcam.Geometry.PointUtils import *
from pymkcam.Geometry.utils import sqrt, number, epsilon
import math
TRANSFORMATIONS = {
"normal": ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0)),
"x": ((1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0)),
"y": ((0, 0, -1, 0), (0, 1, 0, 0), (1, 0, 0, 0)),
"z": ((0, 1, 0, 0), (-1, 0, 0, 0), (0, 0, 1, 0)),
"x_swap_y": ((0, 1, 0, 0), (1, 0, 0, 0), (0, 0, 1, 0)),
"x_swap_z": ((0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0)),
"y_swap_z": ((1, 0, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0)),
"xy_mirror": ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0)),
"xz_mirror": ((1, 0, 0, 0), (0, -1, 0, 0), (0, 0, 1, 0)),
"yz_mirror": ((-1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0)),
}
def get_dot_product(a, b):
""" calculate the dot product of two 3d vectors
@type a: tuple(float) | list(float)
@value a: the first vector to be multiplied
@type b: tuple(float) | list(float)
@value b: the second vector to be multiplied
@rtype: float
@return: the dot product is (a0*b0 + a1*b1 + a2*b2)
"""
return sum(l1 * l2 for l1, l2 in zip(a, b))
def get_length(vector):
""" calculate the lengt of a 3d vector
@type vector: tuple(float) | list(float)
@value vector: the given 3d vector
@rtype: float
@return: the length of a vector is the square root of the dot product
of the vector with itself
"""
return sqrt(get_dot_product(vector, vector))
def get_rotation_matrix_from_to(v_orig, v_dest):
""" calculate the rotation matrix used to transform one vector into another
The result is useful for modifying the rotation matrix of a 3d object.
See the "extend_shape" code in each of the cutter classes (for ODE).
The simplest example is the following with the original vector pointing
along the x axis, while the destination vectors goes along the y axis:
get_rotation_matrix((1, 0, 0), (0, 1, 0))
Basically this describes a rotation around the z axis by 90 degrees.
The resulting 3x3 matrix (tuple of tuple of floats) can be multiplied with
any other vector to rotate it in the same way around the z axis.
@type v_orig: tuple(float) | list(float) | pymkcam.Geometry.Point
@value v_orig: the original 3d vector
@type v_dest: tuple(float) | list(float) | pymkcam.Geometry.Point
@value v_dest: the destination 3d vector
@rtype: tuple(tuple(float))
@return: the roation matrix (3x3)
"""
v_orig_length = get_length(v_orig)
v_dest_length = get_length(v_dest)
cross_product = get_length(pcross(v_orig, v_dest))
try:
arcsin = cross_product / (v_orig_length * v_dest_length)
except ZeroDivisionError:
return None
# prevent float inaccuracies to crash the calculation (within limits)
if 1 < arcsin < 1 + epsilon:
arcsin = 1.0
elif -1 - epsilon < arcsin < -1:
arcsin = -1.0
rot_angle = math.asin(arcsin)
# calculate the rotation axis
# The rotation axis is equal to the cross product of the original and
# destination vectors.
rot_axis = pnormalized((v_orig[1] * v_dest[2] - v_orig[2] * v_dest[1],
v_orig[2] * v_dest[0] - v_orig[0] * v_dest[2],
v_orig[0] * v_dest[1] - v_orig[1] * v_dest[0]))
if not rot_axis:
return None
# get the rotation matrix
# see http://www.fastgraph.com/makegames/3drotation/
c = math.cos(rot_angle)
s = math.sin(rot_angle)
t = 1 - c
return ((t * rot_axis[0] * rot_axis[0] + c,
t * rot_axis[0] * rot_axis[1] - s * rot_axis[2],
t * rot_axis[0] * rot_axis[2] + s * rot_axis[1]),
(t * rot_axis[0] * rot_axis[1] + s * rot_axis[2],
t * rot_axis[1] * rot_axis[1] + c,
t * rot_axis[1] * rot_axis[2] - s * rot_axis[0]),
(t * rot_axis[0] * rot_axis[2] - s * rot_axis[1],
t * rot_axis[1] * rot_axis[2] + s * rot_axis[0],
t * rot_axis[2] * rot_axis[2] + c))
def get_rotation_matrix_axis_angle(rot_axis, rot_angle, use_radians=True):
""" calculate rotation matrix for a normalized vector and an angle
see http://mathworld.wolfram.com/RotationMatrix.html
@type rot_axis: tuple(float)
@value rot_axis: the vector describes the rotation axis. Its length should
be 1.0 (normalized).
@type rot_angle: float
@value rot_angle: rotation angle (radiant)
@rtype: tuple(tuple(float))
@return: the roation matrix (3x3)
"""
if not use_radians:
rot_angle *= math.pi / 180
sin = number(math.sin(rot_angle))
cos = number(math.cos(rot_angle))
return ((cos + rot_axis[0]*rot_axis[0]*(1-cos),
rot_axis[0]*rot_axis[1]*(1-cos) - rot_axis[2]*sin,
rot_axis[0]*rot_axis[2]*(1-cos) + rot_axis[1]*sin),
(rot_axis[1]*rot_axis[0]*(1-cos) + rot_axis[2]*sin,
cos + rot_axis[1]*rot_axis[1]*(1-cos),
rot_axis[1]*rot_axis[2]*(1-cos) - rot_axis[0]*sin),
(rot_axis[2]*rot_axis[0]*(1-cos) - rot_axis[1]*sin,
rot_axis[2]*rot_axis[1]*(1-cos) + rot_axis[0]*sin,
cos + rot_axis[2]*rot_axis[2]*(1-cos)))
def multiply_vector_matrix(v, m):
""" Multiply a 3d vector with a 3x3 matrix. The result is a 3d vector.
@type v: tuple(float) | list(float)
@value v: a 3d vector as tuple or list containing three floats
@type m: tuple(tuple(float)) | list(list(float))
@value m: a 3x3 list/tuple of floats
@rtype: tuple(float)
@return: a tuple of 3 floats as the matrix product
"""
if len(m) == 9:
m = [number(value) for value in m]
m = ((m[0], m[1], m[2]), (m[3], m[4], m[5]), (m[6], m[7], m[8]))
else:
new_m = []
for column in m:
new_m.append([number(value) for value in column])
v = [number(value) for value in v]
return (v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2],
v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2],
v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2])
def multiply_matrix_matrix(m1, m2):
def multi(row1, col2):
return (m1[row1][0] * m2[0][col2] + m1[row1][1] * m2[1][col2] \
+ m1[row1][2] * m2[2][col2])
return ((multi(0, 0), multi(0, 1), multi(0, 2)),
(multi(1, 0), multi(1, 1), multi(1, 2)),
(multi(2, 0), multi(2, 1), multi(2, 2)))
def get_inverse_matrix(m):
_a = m[1][1] * m[2][2] - m[1][2] * m[2][1]
_b = m[0][2] * m[2][1] - m[0][1] * m[2][2]
_c = m[0][1] * m[1][2] - m[0][2] * m[1][1]
_d = m[1][2] * m[2][0] - m[1][0] * m[2][2]
_e = m[0][0] * m[2][2] - m[0][2] * m[2][0]
_f = m[0][2] * m[1][0] - m[0][0] * m[1][2]
_g = m[1][0] * m[2][1] - m[1][1] * m[2][0]
_h = m[0][1] * m[2][0] - m[0][0] * m[2][1]
_k = m[0][0] * m[1][1] - m[0][1] * m[1][0]
det = m[0][0] * _a + m[0][1] * _d + m[0][2] * _g
if det == 0:
return None
else:
return ((_a / det, _b / det, _c / det),
(_d / det, _e / det, _f / det),
(_g / det, _h / det, _k / det))
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
Copyright 2008 Lode Leroy
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
""" the points of a path are only used for describing coordinates. Thus we
don't really need complete "Point" instances that consume a lot of memory.
Since python 2.6 the "namedtuple" factory is available.
This reduces the memory consumption of a toolpath down to 1/3.
"""
try:
# this works for python 2.6 or above (saves memory)
# TODO: disabled for now - check if we could enable it later ...
import INVALID_IMPORT
from collections import namedtuple
tuple_point = namedtuple("TuplePoint", "x y z")
get_point_object = lambda point: tuple_point(point[0], point[1], point[2])
except ImportError:
# dummy for python < v2.6 (consumes more memory)
get_point_object = lambda point: point
from pymkcam.Geometry import IDGenerator
class Path(IDGenerator):
def __init__(self):
super(Path, self).__init__()
self.top_join = None
self.bot_join = None
self.winding = 0
self.points = []
def __repr__(self):
text = ""
text += "path %d: " % self.id
first = True
for point in self.points:
if first:
first = False
else:
text += "-"
text += "%d(%g,%g,%g)" % (id(point), point[0], point[1], point[2])
return text
def insert(self, index, point):
self.points.insert(index, get_point_object(point))
def append(self, point):
self.points.append(get_point_object(point))
def reverse(self):
self.points.reverse()
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2008-2009 Lode Leroy
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam.Geometry import TransformableContainer, IDGenerator
from pymkcam.Geometry.utils import INFINITE, epsilon
from pymkcam.Geometry.PointUtils import *
# "Line" is imported later to avoid circular imports
#from pymkcam.Geometry.Line import Line
class Plane(IDGenerator, TransformableContainer):
__slots__ = ["id", "p", "n"]
def __init__(self, point, normal=None):
super(Plane, self).__init__()
if normal is None:
normal = (0, 0, 1, 'v')
self.p = point
self.n = normal
if not len(self.n) > 3:
self.n = (self.n[0], self.n[1], self.n[2], 'v')
def __repr__(self):
return "Plane<%s,%s>" % (self.p, self.n)
def __cmp__(self, other):
if self.__class__ == other.__class__:
if self.p == other.p:
return cmp(self.n, other.n)
else:
return cmp(self.p, other.p)
else:
return cmp(str(self), str(other))
def copy(self):
return self.__class__(self.p, self.n)
def next(self):
yield "p"
yield "n"
def get_children_count(self):
# a plane always consists of two points
return 2
def reset_cache(self):
# we need to prevent the "normal" from growing
norm = pnormalized(self.n)
if norm:
self.n = norm
def intersect_point(self, direction, point):
if (not direction is None) and (pnorm(direction) != 1):
# calculations will go wrong, if the direction is not a unit vector
direction = pnormalized(direction)
if direction is None:
return (None, INFINITE)
denom = pdot(self.n, direction)
if denom == 0:
return (None, INFINITE)
l = -(pdot(self.n, point) - pdot(self.n, self.p)) / denom
cp = padd(point, pmul(direction, l))
return (cp, l)
def intersect_triangle(self, triangle, counter_clockwise=False):
""" Returns the line of intersection of a triangle with a plane.
"None" is returned, if:
- the triangle does not intersect with the plane
- all vertices of the triangle are on the plane
The line always runs clockwise through the triangle.
"""
# don't import Line in the header -> circular import
from pymkcam.Geometry.Line import Line
collisions = []
for edge, point in ((triangle.e1, triangle.p1),
(triangle.e2, triangle.p2),
(triangle.e3, triangle.p3)):
cp, l = self.intersect_point(edge.dir, point)
# filter all real collisions
# We don't want to count vertices double -> thus we only accept
# a distance that is lower than the length of the edge.
if (not cp is None) and (-epsilon < l < edge.len - epsilon):
collisions.append(cp)
elif (cp is None) and (pdot(self.n, edge.dir) == 0):
cp, dist = self.intersect_point(self.n, point)
if abs(dist) < epsilon:
# the edge is on the plane
collisions.append(point)
if len(collisions) == 3:
# All points of the triangle are on the plane.
# We don't return a waterline, as there should be another non-flat
# triangle with the same waterline.
return None
if len(collisions) == 2:
collision_line = Line(collisions[0], collisions[1])
# no further calculation, if the line is zero-sized
if collision_line.len == 0:
return collision_line
cross = pcross(self.n, collision_line.dir)
if (pdot(cross, triangle.normal) < 0) == bool(not counter_clockwise):
# anti-clockwise direction -> revert the direction of the line
collision_line = Line(collision_line.p2, collision_line.p1)
return collision_line
elif len(collisions) == 1:
# only one point is on the plane
# This waterline (with zero length) should be of no use.
return None
else:
return None
def get_point_projection(self, point):
return self.intersect_point(self.n, point)[0]
def get_line_projection(self, line):
# don't import Line in the header -> circular import
from pymkcam.Geometry.Line import Line
proj_p1 = self.get_point_projection(line.p1)
proj_p2 = self.get_point_projection(line.p2)
return Line(proj_p1, proj_p2)
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2009 Lode Leroy
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam.Geometry.utils import epsilon
from pymkcam.Geometry.kdtree import Node, kdtree
class PointKdtree(kdtree):
__slots__ = ["_n", "tolerance"]
def __init__(self, points=None, cutoff=5, cutoff_distance=0.5,
tolerance=epsilon):
if points is None:
points = []
self._n = None
self.tolerance = tolerance
nodes = []
for p in points:
n = Node(p, p)
nodes.append(n)
kdtree.__init__(self, nodes, cutoff, cutoff_distance)
def dist(self, n1, n2):
dx = n1.bound[0]-n2.bound[0]
dy = n1.bound[1]-n2.bound[1]
dz = n1.bound[2]-n2.bound[2]
return dx*dx+dy*dy+dz*dz
def Point(self, x, y, z):
if self._n:
n = self._n
n.bound = (x, y, z)
else:
n = Node(None, (x, y, z))
(nn, dist) = self.nearest_neighbor(n, self.dist)
if nn and (dist < self.tolerance):
self._n = n
return nn.obj
else:
n.obj = (x, y, z)
self._n = None
self.insert(n)
return n.obj
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
Copyright 2008-2009 Lode Leroy
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam.Geometry.utils import epsilon, sqrt, number
def pnorm(a):
return sqrt(pdot(a,a))
def pnormsq(a):
return pdot(a,a)
def pdist(a, b, axes=None):
return sqrt(pdist_sq(a, b, axes=axes))
def pdist_sq(a, b, axes=None):
if axes is None:
axes = (0, 1, 2)
return sum([(a[index] - b[index]) ** 2 for index in axes])
def pnear(a, b, axes=None):
return pcmp(a, b, axes=axes) == 0
def pcmp(a, b, axes=None):
""" Two points are equal if all dimensions are identical.
Otherwise the result is based on the individual x/y/z comparisons.
"""
if axes is None:
axes = (0, 1, 2)
for axis in axes:
if abs(a[axis] - b[axis]) > epsilon:
return cmp(a[axis], b[axis])
# both points are at the same position
return 0
def ptransform_by_matrix(a, matrix):
if len(a) > 3:
return (a[0] * matrix[0][0] + a[1] * matrix[0][1] + a[2] * matrix[0][2],
a[0] * matrix[1][0] + a[1] * matrix[1][1] + a[2] * matrix[1][2],
a[0] * matrix[2][0] + a[1] * matrix[2][1] + a[2] * matrix[2][2]) + a[3:]
else:
# accept 3x4 matrices as well as 3x3 matrices
offsets = []
for column in matrix:
if len(column) < 4:
offsets.append(0)
else:
offsets.append(column[3])
return (a[0] * matrix[0][0] + a[1] * matrix[0][1] + a[2] * matrix[0][2] + offsets[0],
a[0] * matrix[1][0] + a[1] * matrix[1][1] + a[2] * matrix[1][2] + offsets[1],
a[0] * matrix[2][0] + a[1] * matrix[2][1] + a[2] * matrix[2][2] + offsets[2])
def pmul(a, c):
c = number(c)
return (a[0] * c, a[1] * c, a[2] * c)
def pdiv(a, c):
c = number(c)
return (a[0] / c, a[1] / c, a[2] / c)
def padd(a, b):
return (a[0] + b[0], a[1] + b[1], a[2] + b[2])
def psub(a, b):
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
def pdot(a, b):
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
def pcross(a, b):
return (a[1] * b[2] - b[1] * a[2], b[0] * a[2] - a[0] * b[2], a[0] * b[1] - b[0] * a[1])
def pnormalized(a):
n = pnorm(a)
if n == 0:
return None
else:
return (a[0] / n, a[1] / n, a[2] / n) + a[3:]
def pis_inside(a, minx=None, maxx=None, miny=None, maxy=None, minz=None, maxz=None):
return ((minx is None) or (minx - epsilon <= a[0])) \
and ((maxx is None) or (a[0] <= maxx + epsilon)) \
and ((miny is None) or (miny - epsilon <= a[1])) \
and ((maxy is None) or (a[1] <= maxy + epsilon)) \
and ((minz is None) or (minz - epsilon <= a[2])) \
and ((maxz is None) or (a[2] <= maxz + epsilon))
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2008-2010 Lode Leroy
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam.Geometry.PointUtils import *
from pymkcam.Geometry.Plane import Plane
from pymkcam.Geometry.Line import Line
from pymkcam.Geometry import TransformableContainer, IDGenerator
import pymkcam.Utils.log
try:
import OpenGL.GL as GL
import OpenGL.GLU as GLU
import OpenGL.GLUT as GLUT
GL_enabled = True
except ImportError:
GL_enabled = False
class Triangle(IDGenerator, TransformableContainer):
__slots__ = ["id", "p1", "p2", "p3", "normal", "minx", "maxx", "miny",
"maxy", "minz", "maxz", "e1", "e2", "e3", "normal", "center",
"radius", "radiussq", "middle"]
def __init__(self, p1=None, p2=None, p3=None, n=None):
# points are expected to be in ClockWise order
super(Triangle, self).__init__()
self.p1 = p1
self.p2 = p2
self.p3 = p3
self.normal = n
self.reset_cache()
def reset_cache(self):
self.minx = min(self.p1[0], self.p2[0], self.p3[0])
self.miny = min(self.p1[1], self.p2[1], self.p3[1])
self.minz = min(self.p1[2], self.p2[2], self.p3[2])
self.maxx = max(self.p1[0], self.p2[0], self.p3[0])
self.maxy = max(self.p1[1], self.p2[1], self.p3[1])
self.maxz = max(self.p1[2], self.p2[2], self.p3[2])
self.e1 = Line(self.p1, self.p2)
self.e2 = Line(self.p2, self.p3)
self.e3 = Line(self.p3, self.p1)
# calculate normal, if p1-p2-pe are in clockwise order
if self.normal is None:
self.normal = pnormalized(pcross(psub(self.p3, self.p1), psub(self.p2, self.p1)))
if not len(self.normal) > 3:
self.normal = (self.normal[0], self.normal[1], self.normal[2], 'v')
self.center = pdiv(padd(padd(self.p1, self.p2), self.p3), 3)
self.plane = Plane(self.center, self.normal)
# calculate circumcircle (resulting in radius and middle)
denom = pnorm(pcross(psub(self.p2, self.p1), psub(self.p3, self.p2)))
self.radius = (pdist(self.p2, self.p1) * pdist(self.p3, self.p2) * pdist(self.p3, self.p1)) / (2 * denom)
self.radiussq = self.radius ** 2
denom2 = 2 * denom * denom
alpha = pdist_sq(self.p3, self.p2) * pdot(psub(self.p1, self.p2), psub(self.p1, self.p3)) / denom2
beta = pdist_sq(self.p1, self.p3) * pdot(psub(self.p2, self.p1), psub(self.p2, self.p3)) / denom2
gamma = pdist_sq(self.p1, self.p2) * pdot(psub(self.p3, self.p1), psub(self.p3, self.p2)) / denom2
self.middle = (self.p1[0] * alpha + self.p2[0] * beta + self.p3[0] * gamma,
self.p1[1] * alpha + self.p2[1] * beta + self.p3[1] * gamma,
self.p1[2] * alpha + self.p2[2] * beta + self.p3[2] * gamma)
def __repr__(self):
return "Triangle%d<%s,%s,%s>" % (self.id, self.p1, self.p2, self.p3)
def copy(self):
return self.__class__(self.p1, self.p2, self.p3,
self.normal)
def next(self):
yield "p1"
yield "p2"
yield "p3"
yield "normal"
def get_points(self):
return (self.p1, self.p2, self.p3)
def get_children_count(self):
# tree points per triangle
return 7
def to_OpenGL(self, color=None, show_directions=False):
if not GL_enabled:
return
if not color is None:
GL.glColor4f(*color)
GL.glBegin(GL.GL_TRIANGLES)
# use normals to improve lighting (contributed by imyrek)
normal_t = self.normal
GL.glNormal3f(normal_t[0], normal_t[1], normal_t[2])
# The triangle's points are in clockwise order, but GL expects
# counter-clockwise sorting.
GL.glVertex3f(self.p1[0], self.p1[1], self.p1[2])
GL.glVertex3f(self.p3[0], self.p3[1], self.p3[2])
GL.glVertex3f(self.p2[0], self.p2[1], self.p2[2])
GL.glEnd()
if show_directions: # display surface normals
n = self.normal
c = self.center
d = 0.5
GL.glBegin(GL.GL_LINES)
GL.glVertex3f(c[0], c[1], c[2])
GL.glVertex3f(c[0]+n[0]*d, c[1]+n[1]*d, c[2]+n[2]*d)
GL.glEnd()
if False: # display bounding sphere
GL.glPushMatrix()
middle = self.middle
GL.glTranslate(middle[0], middle[1], middle[2])
if not hasattr(self, "_sphere"):
self._sphere = GLU.gluNewQuadric()
GLU.gluSphere(self._sphere, self.radius, 10, 10)
GL.glPopMatrix()
if pymkcam.Utils.log.is_debug(): # draw triangle id on triangle face
GL.glPushMatrix()
c = self.center
GL.glTranslate(c[0], c[1], c[2])
p12 = pmul(padd(self.p1, self.p2), 0.5)
p3_12 = pnormalized(psub(self.p3, p12))
p2_1 = pnormalized(psub(self.p1, self.p2))
pn = pcross(p2_1, p3_12)
GL.glMultMatrixf((p2_1[0], p2_1[1], p2_1[2], 0, p3_12[0], p3_12[1],
p3_12[2], 0, pn[0], pn[1], pn[2], 0, 0, 0, 0, 1))
n = pmul(self.normal, 0.01)
GL.glTranslatef(n[0], n[1], n[2])
maxdim = max((self.maxx - self.minx), (self.maxy - self.miny),
(self.maxz - self.minz))
factor = 0.001
GL.glScalef(factor * maxdim, factor * maxdim, factor * maxdim)
w = 0
id_string = "%s." % str(self.id)
for ch in id_string:
w += GLUT.glutStrokeWidth(GLUT.GLUT_STROKE_ROMAN, ord(ch))
GL.glTranslate(-w/2, 0, 0)
for ch in id_string:
GLUT.glutStrokeCharacter(GLUT.GLUT_STROKE_ROMAN, ord(ch))
GL.glPopMatrix()
if False: # draw point id on triangle face
c = self.center
p12 = pmul(padd(self.p1, self.p2), 0.5)
p3_12 = pnormalized(psub(self.p3, p12))
p2_1 = pnormalized(psub(self.p1, self.p2))
pn = pcross(p2_1, p3_12)
n = pmul(self.normal, 0.01)
for p in (self.p1, self.p2, self.p3):
GL.glPushMatrix()
pp = psub(p, pmul(psub(p, c), 0.3))
GL.glTranslate(pp[0], pp[1], pp[2])
GL.glMultMatrixf((p2_1[0], p2_1[1], p2_1[2], 0, p3_12[0], p3_12[1],
p3_12[2], 0, pn[0], pn[1], pn[2], 0, 0, 0, 0, 1))
GL.glTranslatef(n[0], n[1], n[2])
GL.glScalef(0.001, 0.001, 0.001)
w = 0
for ch in str(p.id):
w += GLUT.glutStrokeWidth(GLUT.GLUT_STROKE_ROMAN, ord(ch))
GL.glTranslate(-w/2, 0, 0)
for ch in str(p.id):
GLUT.glutStrokeCharacter(GLUT.GLUT_STROKE_ROMAN, ord(ch))
GL.glPopMatrix()
def is_point_inside(self, p):
# http://www.blackpawn.com/texts/pointinpoly/default.html
# Compute vectors
v0 = psub(self.p3, self.p1)
v1 = psub(self.p2, self.p1)
v2 = psub(p, self.p1)
# Compute dot products
dot00 = pdot(v0, v0)
dot01 = pdot(v0, v1)
dot02 = pdot(v0, v2)
dot11 = pdot(v1, v1)
dot12 = pdot(v1, v2)
# Compute barycentric coordinates
denom = dot00 * dot11 - dot01 * dot01
if denom == 0:
return False
invDenom = 1.0 / denom
# Originally, "u" and "v" are multiplied with "1/denom".
# We don't do this to avoid division by zero (for triangles that are
# "almost" invalid).
u = (dot11 * dot02 - dot01 * dot12) * invDenom
v = (dot00 * dot12 - dot01 * dot02) * invDenom
# Check if point is in triangle
return (u > 0) and (v > 0) and (u + v < 1)
def subdivide(self, depth):
sub = []
if depth == 0:
sub.append(self)
else:
p4 = pdiv(padd(self.p1, self.p2), 2)
p5 = pdiv(padd(self.p2, self.p3), 2)
p6 = pdiv(padd(self.p3, self.p1), 2)
sub += Triangle(self.p1, p4, p6).subdivide(depth - 1)
sub += Triangle(p6, p5, self.p3).subdivide(depth - 1)
sub += Triangle(p6, p4, p5).subdivide(depth - 1)
sub += Triangle(p4, self.p2, p5).subdivide(depth - 1)
return sub
def get_area(self):
cross = pcross(psub(self.p2, self.p1), psub(self.p3, self.p1))
return pnorm(cross) / 2
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2008-2009 Lode Leroy
This file is part of pyMKcam.
pyMKcam 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 3 of the License, or
(at your option) any later version.
pyMKcam 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 pyMKcam. If not, see <http://www.gnu.org/licenses/>.
"""
from pymkcam.Geometry.kdtree import kdtree, Node
overlaptest = True
def SearchKdtree2d(tree, minx, maxx, miny, maxy):
if tree.bucket:
triangles = []
for n in tree.nodes:
if not overlaptest:
triangles.append(n.obj)
else:
if not ((n.bound[0] > maxx) or
(n.bound[1] < minx) or
(n.bound[2] > maxy) or
(n.bound[3] < miny)):
triangles.append(n.obj)
return triangles
else:
if tree.cutdim == 0:
if maxx < tree.minval:
return []
elif maxx < tree.cutval:
return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy)
else:
return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \
+ SearchKdtree2d(tree.hi, minx, maxx, miny, maxy)
elif tree.cutdim == 1:
if minx > tree.maxval:
return []
elif minx > tree.cutval:
return SearchKdtree2d(tree.hi, minx, maxx, miny, maxy)
else:
return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \
+ SearchKdtree2d(tree.hi, minx, maxx, miny, maxy)
elif tree.cutdim == 2:
if maxy < tree.minval:
return []
elif maxy < tree.cutval:
return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy)
else:
return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \
+ SearchKdtree2d(tree.hi, minx, maxx, miny, maxy)
elif tree.cutdim == 3:
if miny > tree.maxval:
return []
elif miny > tree.cutval:
return SearchKdtree2d(tree.hi, minx, maxx, miny, maxy)
else:
return SearchKdtree2d(tree.lo, minx, maxx, miny, maxy) \
+ SearchKdtree2d(tree.hi, minx, maxx, miny, maxy)
class TriangleKdtree(kdtree):
__slots__ = []
def __init__(self, triangles, cutoff=3, cutoff_distance=1.0):
nodes = []
for t in triangles:
n = Node(t, (min(t.p1[0], t.p2[0], t.p3[0]),
max(t.p1[0], t.p2[0], t.p3[0]),
min(t.p1[1], t.p2[1], t.p3[1]),
max(t.p1[1], t.p2[1], t.p3[1])))
nodes.append(n)
super(TriangleKdtree, self).__init__(nodes, cutoff, cutoff_distance)
def Search(self, minx, maxx, miny, maxy):
return SearchKdtree2d(self, minx, maxx, miny, maxy)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2008 Lode Leroy
This file is part of PyCAM.
PyCAM 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 3 of the License, or
(at your option) any later version.
PyCAM 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 PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import decimal
import math
INFINITE = 100000
epsilon = 0.00001
# use the "decimal" module for fixed precision numbers (only for debugging)
_use_precision = False
# the lambda functions below are more efficient than function definitions
if _use_precision:
ceil = lambda value: int((value + number(1).next_minus()) // 1)
else:
ceil = lambda value: int(math.ceil(value))
# return "0" for "-epsilon < value < 0" (to work around floating inaccuracies)
# otherwise: return the sqrt function of the current type (could even raise
# exceptions)
if _use_precision:
sqrt = lambda value: (((value < -epsilon) or (value > 0)) and \
value.sqrt()) or 0
else:
sqrt = lambda value: (((value < -epsilon) or (value > 0)) and \
math.sqrt(value)) or 0
if _use_precision:
number = lambda value: decimal.Decimal(str(value))
else:
number = float
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2008 Lode Leroy
This file is part of PyCAM.
PyCAM 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 3 of the License, or
(at your option) any later version.
PyCAM 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 PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
__all__ = ["common", "Console", "OpenGLTools", "Project", "Settings",
"Vizualisation"]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM 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 3 of the License, or
(at your option) any later version.
PyCAM 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 PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
__all__ = ["ode"]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment