Merge branch 'master' into OCP
This commit is contained in:
@ -1,35 +0,0 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
runtests:
|
||||
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
|
||||
environment:
|
||||
PYTEST_QT_API: pyqt5
|
||||
PYTHON_VERSION: 3.6
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- run: HOMEBREW_NO_AUTO_UPDATE=1 brew install wget
|
||||
- run: cd && rm -rf ~/.pyenv && rm -rf ~/virtualenvs
|
||||
- run: wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O ~/miniconda.sh
|
||||
- run: chmod +x ~/miniconda.sh && ~/miniconda.sh -b
|
||||
- run: echo "export PATH=~/miniconda3/bin:$PATH" >> $BASH_ENV
|
||||
- run: conda config --set always_yes yes
|
||||
- run: conda create --quiet --name cqtest -c cadquery -c conda-forge -c dlr-sc pythonocc-core=0.18.2 oce=0.18.2 python=$PYTHON_VERSION pyparsing mock lldb
|
||||
- run: |
|
||||
source activate cqtest
|
||||
pip install coverage
|
||||
python setup.py install
|
||||
conda env list
|
||||
conda list
|
||||
coverage run runtests.py
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
workflow:
|
||||
jobs:
|
||||
- runtests
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ dist/*
|
||||
.idea/*
|
||||
cadquery.egg-info
|
||||
target/*
|
||||
.vscode
|
||||
|
50
.travis.yml
50
.travis.yml
@ -5,7 +5,6 @@ dist: trusty
|
||||
|
||||
branches:
|
||||
only:
|
||||
- adam-urbanczyk-OCC-version-update
|
||||
- master
|
||||
- "/\\d+\\.\\d+\\.?d*\\-*[a-z]*/"
|
||||
|
||||
@ -25,36 +24,37 @@ env:
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: TRAVIS_PYTHON_VERSION=3.6
|
||||
- name: "Python 3.6 - osx"
|
||||
env: PYTHON_VERSION=3.6
|
||||
os: osx
|
||||
- env: TRAVIS_PYTHON_VERSION=3.6
|
||||
- name: "Python 3.6 - linux"
|
||||
env: PYTHON_VERSION=3.6
|
||||
os: linux
|
||||
- env: TRAVIS_PYTHON_VERSION=3.7
|
||||
- name: "Python 3.7 - osx"
|
||||
env: PYTHON_VERSION=3.7
|
||||
os: osx
|
||||
- env: TRAVIS_PYTHON_VERSION=3.7
|
||||
- name: "Python 3.7 - linux"
|
||||
env: PYTHON_VERSION=3.7
|
||||
os: linux
|
||||
- name: "Lint"
|
||||
env: PYTHON_VERSION=3.7
|
||||
os: linux
|
||||
script:
|
||||
- black . --diff --check
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
|
||||
PY_MAJOR=2 ;
|
||||
else
|
||||
PY_MAJOR=3 ;
|
||||
fi ;
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
OS=Linux ;
|
||||
else
|
||||
OS=MacOSX ;
|
||||
fi ;
|
||||
wget https://repo.continuum.io/miniconda/Miniconda$PY_MAJOR-latest-$OS-x86_64.sh -O miniconda.sh
|
||||
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-$OS-x86_64.sh -O miniconda.sh
|
||||
- bash miniconda.sh -b -p $HOME/miniconda;
|
||||
- export PATH="$HOME/miniconda/bin:$HOME/miniconda/lib:$PATH";
|
||||
- hash -r;
|
||||
- conda config --set always_yes yes --set changeps1 no;
|
||||
- conda create -y -q -n test_cq -c cadquery -c conda-forge pythonocc-core=0.18.2 oce=0.18.2 python=$TRAVIS_PYTHON_VERSION
|
||||
pyparsing mock;
|
||||
- source ~/miniconda/bin/activate test_cq;
|
||||
- python -c 'import OCC.gp as gp; print(gp.gp_Vec())'
|
||||
- pip install codecov
|
||||
- conda env create -f environment.yml
|
||||
- source ~/miniconda/bin/activate cadquery
|
||||
- conda install -c conda-forge -c defaults -c cadquery python=$PYTHON_VERSION
|
||||
|
||||
install:
|
||||
- python setup.py install
|
||||
@ -64,7 +64,7 @@ before_script:
|
||||
- sudo rm -f /cores/core.*
|
||||
|
||||
script:
|
||||
- coverage run runtests.py
|
||||
- pytest -v --cov
|
||||
|
||||
after_success:
|
||||
- codecov
|
||||
@ -72,15 +72,3 @@ after_success:
|
||||
after_failure:
|
||||
- ls /cores/core.*
|
||||
- lldb --core `ls /cores/core.*` --batch --one-line "bt"
|
||||
|
||||
|
||||
before_deploy:
|
||||
- conda install anaconda-client conda-build
|
||||
- sh conda_build.sh
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: anaconda -v -t $ANACONDA_TOKEN upload --force --user cadquery /tmp/cbld/**/cadquery-*.tar.bz2
|
||||
on:
|
||||
tags: true
|
||||
|
32
README.md
32
README.md
@ -123,6 +123,38 @@ You do not need to be a software developer to have a big impact on this project.
|
||||
|
||||
It is asked that all contributions to this project be made in a respectful and considerate way. Please use the [Python Community Code of Conduct's](https://www.python.org/psf/codeofconduct/) guidelines as a reference.
|
||||
|
||||
### Contributing code
|
||||
|
||||
If you are going to contribute code, make sure to follow this steps:
|
||||
|
||||
- Consider opening an issue first to discuss what you have in mind
|
||||
- Try to keep it as short and simple as possible (if you want to change several
|
||||
things, start with just one!)
|
||||
- Fork the CadQuery repository, clone your fork and create a new branch to
|
||||
start working on your changes
|
||||
- Start with the tests! How should CadQuery behave after your changes? Make
|
||||
sure to add some tests to the test suite to ensure proper behavior
|
||||
- Make sure your tests have assertions checking all the expected results
|
||||
- Add a nice docstring to the test indicating what the test is doing; if there
|
||||
is too much to explain, consider splitting the test in two!
|
||||
- Go ahead and implement the changes
|
||||
- Add a nice docstring to the functions/methods/classes you implement
|
||||
describing what they do, what the expected parameters are and what it returns
|
||||
(if anything)
|
||||
- Update the documentation if there is any change to the public API
|
||||
- Consider adding an example to the documentation showing your cool new
|
||||
feature!
|
||||
- Make sure nothing is broken (run the complete test suite with `pytest`)
|
||||
- Run `black` to autoformat your code and make sure your code style complies
|
||||
with CadQuery's
|
||||
- Push the changes to your fork and open a pull-request upstream
|
||||
- Keep an eye on the automated feedback you will receive from the CI pipelines;
|
||||
if there is a test failing or some code is not properly formatted, you will
|
||||
be notified without human intervention
|
||||
- Be prepared for constructive feedback and criticism!
|
||||
- Be patient and respectful, remember that those reviewing your code are also
|
||||
working hard (sometimes reviewing changes is harder than implementing them!)
|
||||
|
||||
### How to Report a Bug
|
||||
When filing a bug report [issue](https://github.com/CadQuery/cadquery/issues), please be sure to answer these questions:
|
||||
|
||||
|
28
appveyor.yml
28
appveyor.yml
@ -17,34 +17,14 @@ install:
|
||||
- set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
|
||||
- conda config --set always_yes yes
|
||||
- conda update -q conda
|
||||
- conda create --quiet --name cqtest -c cadquery -c conda-forge -c dlr-sc pythonocc-core=0.18.2 python=%PYTHON_VERSION% pyparsing mock coverage codecov
|
||||
- activate cqtest
|
||||
- pip install codecov
|
||||
- python setup.py install
|
||||
- conda env create -f environment.yml
|
||||
- activate cadquery
|
||||
- conda install -c conda-forge -c defaults -c cadquery python=%PYTHON_VERSION%
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- coverage run runtests.py
|
||||
- pytest -v --cov
|
||||
|
||||
on_success:
|
||||
- codecov
|
||||
|
||||
#deploy:
|
||||
#- provider: Script
|
||||
# on:
|
||||
# APPVEYOR_REPO_TAG: true
|
||||
|
||||
#before_deploy:
|
||||
# - set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
|
||||
# - set "TRAVIS_TAG=%APPVEYOR_REPO_TAG_NAME%"
|
||||
# - set "TRAVIS_COMMIT=%APPVEYOR_REPO_COMMIT%"
|
||||
# - set "TRAVIS_PYTHON_VERSION=%PYTHON_VERSION%"
|
||||
# - conda config --set always_yes yes
|
||||
# - activate cqtest
|
||||
# - conda install anaconda-client conda-build
|
||||
# - conda info --envs
|
||||
# - call conda_build.bat
|
||||
|
||||
#deploy_script:
|
||||
# - anaconda -v -t %ANACONDA_TOKEN% upload --force --user cadquery ./win-64/cadquery-*.tar.bz2
|
||||
|
@ -1,25 +1,65 @@
|
||||
# these items point to the OCC implementation
|
||||
from .occ_impl.geom import Plane, BoundBox, Vector, Matrix
|
||||
from .occ_impl.shapes import (Shape, Vertex, Edge, Face, Wire, Solid, Shell,
|
||||
Compound, sortWiresByBuildOrder)
|
||||
from .occ_impl.shapes import (
|
||||
Shape,
|
||||
Vertex,
|
||||
Edge,
|
||||
Face,
|
||||
Wire,
|
||||
Solid,
|
||||
Shell,
|
||||
Compound,
|
||||
sortWiresByBuildOrder,
|
||||
)
|
||||
from .occ_impl import exporters
|
||||
from .occ_impl import importers
|
||||
|
||||
# these items are the common implementation
|
||||
|
||||
# the order of these matter
|
||||
from .selectors import (NearestToPointSelector, ParallelDirSelector,
|
||||
DirectionSelector, PerpendicularDirSelector, TypeSelector,
|
||||
DirectionMinMaxSelector, StringSyntaxSelector, Selector)
|
||||
from .selectors import (
|
||||
NearestToPointSelector,
|
||||
ParallelDirSelector,
|
||||
DirectionSelector,
|
||||
PerpendicularDirSelector,
|
||||
TypeSelector,
|
||||
DirectionMinMaxSelector,
|
||||
StringSyntaxSelector,
|
||||
Selector,
|
||||
)
|
||||
from .cq import CQ, Workplane, selectors
|
||||
from . import plugins
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CQ', 'Workplane', 'plugins', 'selectors', 'Plane', 'BoundBox', 'Matrix', 'Vector', 'sortWiresByBuildOrder',
|
||||
'Shape', 'Vertex', 'Edge', 'Wire', 'Face', 'Solid', 'Shell', 'Compound', 'exporters', 'importers',
|
||||
'NearestToPointSelector', 'ParallelDirSelector', 'DirectionSelector', 'PerpendicularDirSelector',
|
||||
'TypeSelector', 'DirectionMinMaxSelector', 'StringSyntaxSelector', 'Selector', 'plugins'
|
||||
"CQ",
|
||||
"Workplane",
|
||||
"plugins",
|
||||
"selectors",
|
||||
"Plane",
|
||||
"BoundBox",
|
||||
"Matrix",
|
||||
"Vector",
|
||||
"sortWiresByBuildOrder",
|
||||
"Shape",
|
||||
"Vertex",
|
||||
"Edge",
|
||||
"Wire",
|
||||
"Face",
|
||||
"Solid",
|
||||
"Shell",
|
||||
"Compound",
|
||||
"exporters",
|
||||
"importers",
|
||||
"NearestToPointSelector",
|
||||
"ParallelDirSelector",
|
||||
"DirectionSelector",
|
||||
"PerpendicularDirSelector",
|
||||
"TypeSelector",
|
||||
"DirectionMinMaxSelector",
|
||||
"StringSyntaxSelector",
|
||||
"Selector",
|
||||
"plugins",
|
||||
]
|
||||
|
||||
__version__ = "2.0.0dev"
|
||||
__version__ = "2.0RC1"
|
||||
|
774
cadquery/cq.py
774
cadquery/cq.py
File diff suppressed because it is too large
Load Diff
@ -20,13 +20,22 @@ template = """
|
||||
</div>
|
||||
|
||||
"""
|
||||
template_content_indent = ' '
|
||||
template_content_indent = " "
|
||||
|
||||
|
||||
def cq_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
def cq_directive(
|
||||
name,
|
||||
arguments,
|
||||
options,
|
||||
content,
|
||||
lineno,
|
||||
content_offset,
|
||||
block_text,
|
||||
state,
|
||||
state_machine,
|
||||
):
|
||||
# only consider inline snippets
|
||||
plot_code = '\n'.join(content)
|
||||
plot_code = "\n".join(content)
|
||||
|
||||
# Since we don't have a filename, use a hash based on the content
|
||||
# the script must define a variable called 'out', which is expected to
|
||||
@ -52,22 +61,20 @@ def cq_directive(name, arguments, options, content, lineno,
|
||||
lines = []
|
||||
|
||||
# get rid of new lines
|
||||
out_svg = out_svg.replace('\n', '')
|
||||
out_svg = out_svg.replace("\n", "")
|
||||
|
||||
txt_align = "left"
|
||||
if "align" in options:
|
||||
txt_align = options['align']
|
||||
txt_align = options["align"]
|
||||
|
||||
lines.extend((template % locals()).split('\n'))
|
||||
lines.extend((template % locals()).split("\n"))
|
||||
|
||||
lines.extend(['::', ''])
|
||||
lines.extend([' %s' % row.rstrip()
|
||||
for row in plot_code.split('\n')])
|
||||
lines.append('')
|
||||
lines.extend(["::", ""])
|
||||
lines.extend([" %s" % row.rstrip() for row in plot_code.split("\n")])
|
||||
lines.append("")
|
||||
|
||||
if len(lines):
|
||||
state_machine.insert_input(
|
||||
lines, state_machine.input_lines.source(0))
|
||||
state_machine.insert_input(lines, state_machine.input_lines.source(0))
|
||||
|
||||
return []
|
||||
|
||||
@ -77,9 +84,10 @@ def setup(app):
|
||||
setup.config = app.config
|
||||
setup.confdir = app.confdir
|
||||
|
||||
options = {'height': directives.length_or_unitless,
|
||||
'width': directives.length_or_percentage_or_unitless,
|
||||
'align': directives.unchanged
|
||||
}
|
||||
options = {
|
||||
"height": directives.length_or_unitless,
|
||||
"width": directives.length_or_percentage_or_unitless,
|
||||
"align": directives.unchanged,
|
||||
}
|
||||
|
||||
app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options)
|
||||
app.add_directive("cq_plot", cq_directive, True, (0, 2, 0), **options)
|
||||
|
142
cadquery/cqgi.py
142
cadquery/cqgi.py
@ -9,6 +9,7 @@ import cadquery
|
||||
|
||||
CQSCRIPT = "<cqscript>"
|
||||
|
||||
|
||||
def parse(script_source):
|
||||
"""
|
||||
Parses the script as a model, and returns a model.
|
||||
@ -34,6 +35,7 @@ class CQModel(object):
|
||||
|
||||
the build method can be used to generate a 3d model
|
||||
"""
|
||||
|
||||
def __init__(self, script_source):
|
||||
"""
|
||||
Create an object by parsing the supplied python script.
|
||||
@ -100,16 +102,20 @@ class CQModel(object):
|
||||
try:
|
||||
self.set_param_values(build_parameters)
|
||||
collector = ScriptCallback()
|
||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
||||
.add_entry("__name__", "__cqgi__") \
|
||||
.add_entry("show_object", collector.show_object) \
|
||||
.add_entry("debug", collector.debug) \
|
||||
.add_entry("describe_parameter",collector.describe_parameter) \
|
||||
env = (
|
||||
EnvironmentBuilder()
|
||||
.with_real_builtins()
|
||||
.with_cadquery_objects()
|
||||
.add_entry("__name__", "__cqgi__")
|
||||
.add_entry("show_object", collector.show_object)
|
||||
.add_entry("debug", collector.debug)
|
||||
.add_entry("describe_parameter", collector.describe_parameter)
|
||||
.build()
|
||||
)
|
||||
|
||||
c = compile(self.ast_tree, CQSCRIPT, 'exec')
|
||||
exec (c, env)
|
||||
result.set_debug(collector.debugObjects )
|
||||
c = compile(self.ast_tree, CQSCRIPT, "exec")
|
||||
exec(c, env)
|
||||
result.set_debug(collector.debugObjects)
|
||||
result.set_success_result(collector.outputObjects)
|
||||
|
||||
except Exception as ex:
|
||||
@ -124,7 +130,9 @@ class CQModel(object):
|
||||
|
||||
for k, v in params.items():
|
||||
if k not in model_parameters:
|
||||
raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
|
||||
raise InvalidParameterError(
|
||||
"Cannot set value '%s': not a parameter of the model." % k
|
||||
)
|
||||
|
||||
p = model_parameters[k]
|
||||
p.set_value(v)
|
||||
@ -134,10 +142,12 @@ class ShapeResult(object):
|
||||
"""
|
||||
An object created by a build, including the user parameters provided
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.shape = None
|
||||
self.options = None
|
||||
|
||||
|
||||
class BuildResult(object):
|
||||
"""
|
||||
The result of executing a CadQuery script.
|
||||
@ -149,10 +159,11 @@ class BuildResult(object):
|
||||
If unsuccessful, the exception property contains a reference to
|
||||
the stack trace that occurred.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.buildTime = None
|
||||
self.results = [] #list of ShapeResult
|
||||
self.debugObjects = [] #list of ShapeResult
|
||||
self.results = [] # list of ShapeResult
|
||||
self.debugObjects = [] # list of ShapeResult
|
||||
self.first_result = None
|
||||
self.success = False
|
||||
self.exception = None
|
||||
@ -176,13 +187,14 @@ class ScriptMetadata(object):
|
||||
Defines the metadata for a parsed CQ Script.
|
||||
the parameters property is a dict of InputParameter objects.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.parameters = {}
|
||||
|
||||
def add_script_parameter(self, p):
|
||||
self.parameters[p.name] = p
|
||||
|
||||
def add_parameter_description(self,name,description):
|
||||
def add_parameter_description(self, name, description):
|
||||
p = self.parameters[name]
|
||||
p.desc = description
|
||||
|
||||
@ -214,6 +226,7 @@ class InputParameter:
|
||||
provide additional metadata
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
#: the default value for the variable.
|
||||
@ -234,7 +247,9 @@ class InputParameter:
|
||||
self.ast_node = None
|
||||
|
||||
@staticmethod
|
||||
def create(ast_node, var_name, var_type, default_value, valid_values=None, desc=None):
|
||||
def create(
|
||||
ast_node, var_name, var_type, default_value, valid_values=None, desc=None
|
||||
):
|
||||
|
||||
if valid_values is None:
|
||||
valid_values = []
|
||||
@ -251,8 +266,10 @@ class InputParameter:
|
||||
def set_value(self, new_value):
|
||||
if len(self.valid_values) > 0 and new_value not in self.valid_values:
|
||||
raise InvalidParameterError(
|
||||
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} "
|
||||
.format(str(new_value), self.name, str(self.valid_values)))
|
||||
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} ".format(
|
||||
str(new_value), self.name, str(self.valid_values)
|
||||
)
|
||||
)
|
||||
|
||||
if self.varType == NumberParameterType:
|
||||
try:
|
||||
@ -265,28 +282,33 @@ class InputParameter:
|
||||
self.ast_node.n = f
|
||||
except ValueError:
|
||||
raise InvalidParameterError(
|
||||
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric."
|
||||
.format(str(new_value), self.name))
|
||||
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric.".format(
|
||||
str(new_value), self.name
|
||||
)
|
||||
)
|
||||
|
||||
elif self.varType == StringParameterType:
|
||||
self.ast_node.s = str(new_value)
|
||||
elif self.varType == BooleanParameterType:
|
||||
if new_value:
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
self.ast_node.value = True
|
||||
else:
|
||||
self.ast_node.id = 'True'
|
||||
self.ast_node.id = "True"
|
||||
else:
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
self.ast_node.value = False
|
||||
else:
|
||||
self.ast_node.id = 'False'
|
||||
self.ast_node.id = "False"
|
||||
else:
|
||||
raise ValueError("Unknown Type of var: ", str(self.varType))
|
||||
|
||||
def __str__(self):
|
||||
return "InputParameter: {name=%s, type=%s, defaultValue=%s" % (
|
||||
self.name, str(self.varType), str(self.default_value))
|
||||
self.name,
|
||||
str(self.varType),
|
||||
str(self.default_value),
|
||||
)
|
||||
|
||||
|
||||
class ScriptCallback(object):
|
||||
@ -295,22 +317,23 @@ class ScriptCallback(object):
|
||||
the show_object() method is exposed to CQ scripts, to allow them
|
||||
to return objects to the execution environment
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.outputObjects = []
|
||||
self.debugObjects = []
|
||||
|
||||
def show_object(self, shape,options={}):
|
||||
def show_object(self, shape, options={}):
|
||||
"""
|
||||
return an object to the executing environment, with options
|
||||
:param shape: a cadquery object
|
||||
:param options: a dictionary of options that will be made available to the executing environment
|
||||
"""
|
||||
o = ShapeResult()
|
||||
o.options=options
|
||||
o.options = options
|
||||
o.shape = shape
|
||||
self.outputObjects.append(o)
|
||||
|
||||
def debug(self,obj,args={}):
|
||||
def debug(self, obj, args={}):
|
||||
"""
|
||||
Debug print/output an object, with optional arguments.
|
||||
"""
|
||||
@ -319,7 +342,7 @@ class ScriptCallback(object):
|
||||
s.options = args
|
||||
self.debugObjects.append(s)
|
||||
|
||||
def describe_parameter(self,var_data ):
|
||||
def describe_parameter(self, var_data):
|
||||
"""
|
||||
Do Nothing-- we parsed the ast ahead of execution to get what we need.
|
||||
"""
|
||||
@ -335,12 +358,12 @@ class ScriptCallback(object):
|
||||
return len(self.outputObjects) > 0
|
||||
|
||||
|
||||
|
||||
class InvalidParameterError(Exception):
|
||||
"""
|
||||
Raised when an attempt is made to provide a new parameter value
|
||||
that cannot be assigned to the model
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -349,6 +372,7 @@ class NoOutputError(Exception):
|
||||
Raised when the script does not execute the show_object() method to
|
||||
return a solid
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -386,6 +410,7 @@ class EnvironmentBuilder(object):
|
||||
The environment includes the builtins, as well as
|
||||
the other methods the script will need.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.env = {}
|
||||
|
||||
@ -393,12 +418,12 @@ class EnvironmentBuilder(object):
|
||||
return self.with_builtins(__builtins__)
|
||||
|
||||
def with_builtins(self, env_dict):
|
||||
self.env['__builtins__'] = env_dict
|
||||
self.env["__builtins__"] = env_dict
|
||||
return self
|
||||
|
||||
def with_cadquery_objects(self):
|
||||
self.env['cadquery'] = cadquery
|
||||
self.env['cq'] = cadquery
|
||||
self.env["cadquery"] = cadquery
|
||||
self.env["cq"] = cadquery
|
||||
return self
|
||||
|
||||
def add_entry(self, name, value):
|
||||
@ -408,30 +433,33 @@ class EnvironmentBuilder(object):
|
||||
def build(self):
|
||||
return self.env
|
||||
|
||||
|
||||
class ParameterDescriptionFinder(ast.NodeTransformer):
|
||||
"""
|
||||
Visits a parse tree, looking for function calls to describe_parameter(var, description )
|
||||
"""
|
||||
|
||||
def __init__(self, cq_model):
|
||||
self.cqModel = cq_model
|
||||
|
||||
def visit_Call(self,node):
|
||||
"""
|
||||
def visit_Call(self, node):
|
||||
"""
|
||||
Called when we see a function call. Is it describe_parameter?
|
||||
"""
|
||||
try:
|
||||
if node.func.id == 'describe_parameter':
|
||||
try:
|
||||
if node.func.id == "describe_parameter":
|
||||
# looks like we have a call to our function.
|
||||
# first parameter is the variable,
|
||||
# second is the description
|
||||
varname = node.args[0].id
|
||||
desc = node.args[1].s
|
||||
self.cqModel.add_parameter_description(varname,desc)
|
||||
self.cqModel.add_parameter_description(varname, desc)
|
||||
|
||||
except:
|
||||
#print "Unable to handle function call"
|
||||
except:
|
||||
# print "Unable to handle function call"
|
||||
pass
|
||||
return node
|
||||
return node
|
||||
|
||||
|
||||
class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
"""
|
||||
@ -446,24 +474,42 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
|
||||
if type(value_node) == ast.Num:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
|
||||
InputParameter.create(
|
||||
value_node, var_name, NumberParameterType, value_node.n
|
||||
)
|
||||
)
|
||||
elif type(value_node) == ast.Str:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, StringParameterType, value_node.s))
|
||||
InputParameter.create(
|
||||
value_node, var_name, StringParameterType, value_node.s
|
||||
)
|
||||
)
|
||||
elif type(value_node) == ast.Name:
|
||||
if value_node.id == 'True':
|
||||
if value_node.id == "True":
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
elif value_node.id == 'False':
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, True
|
||||
)
|
||||
)
|
||||
elif value_node.id == "False":
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, False))
|
||||
elif hasattr(ast, 'NameConstant') and type(value_node) == ast.NameConstant:
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, False
|
||||
)
|
||||
)
|
||||
elif hasattr(ast, "NameConstant") and type(value_node) == ast.NameConstant:
|
||||
if value_node.value == True:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, True
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, False))
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, False
|
||||
)
|
||||
)
|
||||
except:
|
||||
print("Unable to handle assignment for variable '%s'" % var_name)
|
||||
pass
|
||||
@ -479,7 +525,7 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
|
||||
# Handle the NamedConstant type that is only present in Python 3
|
||||
astTypes = [ast.Num, ast.Str, ast.Name]
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
astTypes.append(ast.NameConstant)
|
||||
|
||||
if type(node.value) in astTypes:
|
||||
|
@ -1,9 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
#from OCP.Visualization import Tesselator
|
||||
|
||||
# from OCP.Visualization import Tesselator
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
import cStringIO as StringIO
|
||||
else:
|
||||
@ -72,7 +74,7 @@ def exportShape(shape, exportType, fileLike, tolerance=0.1):
|
||||
|
||||
# add vertices
|
||||
for v in tess[0]:
|
||||
mesher.addVertex(v.x,v.y,v.z)
|
||||
mesher.addVertex(v.x, v.y, v.z)
|
||||
|
||||
# add triangles
|
||||
for t in tess[1]:
|
||||
@ -112,7 +114,7 @@ def readAndDeleteFile(fileName):
|
||||
return the contents as a string
|
||||
"""
|
||||
res = ""
|
||||
with open(fileName, 'r') as f:
|
||||
with open(fileName, "r") as f:
|
||||
res = "{}".format(f.read())
|
||||
|
||||
os.remove(fileName)
|
||||
@ -148,32 +150,32 @@ class AmfWriter(object):
|
||||
self.tessellation = tessellation
|
||||
|
||||
def writeAmf(self, outFile):
|
||||
amf = ET.Element('amf', units=self.units)
|
||||
amf = ET.Element("amf", units=self.units)
|
||||
# TODO: if result is a compound, we need to loop through them
|
||||
object = ET.SubElement(amf, 'object', id="0")
|
||||
mesh = ET.SubElement(object, 'mesh')
|
||||
vertices = ET.SubElement(mesh, 'vertices')
|
||||
volume = ET.SubElement(mesh, 'volume')
|
||||
object = ET.SubElement(amf, "object", id="0")
|
||||
mesh = ET.SubElement(object, "mesh")
|
||||
vertices = ET.SubElement(mesh, "vertices")
|
||||
volume = ET.SubElement(mesh, "volume")
|
||||
|
||||
# add vertices
|
||||
for v in self.tessellation[0]:
|
||||
vtx = ET.SubElement(vertices, 'vertex')
|
||||
coord = ET.SubElement(vtx, 'coordinates')
|
||||
x = ET.SubElement(coord, 'x')
|
||||
vtx = ET.SubElement(vertices, "vertex")
|
||||
coord = ET.SubElement(vtx, "coordinates")
|
||||
x = ET.SubElement(coord, "x")
|
||||
x.text = str(v.x)
|
||||
y = ET.SubElement(coord, 'y')
|
||||
y = ET.SubElement(coord, "y")
|
||||
y.text = str(v.y)
|
||||
z = ET.SubElement(coord, 'z')
|
||||
z = ET.SubElement(coord, "z")
|
||||
z.text = str(v.z)
|
||||
|
||||
# add triangles
|
||||
for t in self.tessellation[1]:
|
||||
triangle = ET.SubElement(volume, 'triangle')
|
||||
v1 = ET.SubElement(triangle, 'v1')
|
||||
triangle = ET.SubElement(volume, "triangle")
|
||||
v1 = ET.SubElement(triangle, "v1")
|
||||
v1.text = str(t[0])
|
||||
v2 = ET.SubElement(triangle, 'v2')
|
||||
v2 = ET.SubElement(triangle, "v2")
|
||||
v2.text = str(t[1])
|
||||
v3 = ET.SubElement(triangle, 'v3')
|
||||
v3 = ET.SubElement(triangle, "v3")
|
||||
v3.text = str(t[2])
|
||||
|
||||
amf = ET.ElementTree(amf).write(outFile, xml_declaration=True)
|
||||
@ -211,11 +213,11 @@ class JsonMesh(object):
|
||||
|
||||
def toJson(self):
|
||||
return JSON_TEMPLATE % {
|
||||
'vertices': str(self.vertices),
|
||||
'faces': str(self.faces),
|
||||
'nVertices': self.nVertices,
|
||||
'nFaces': self.nFaces
|
||||
};
|
||||
"vertices": str(self.vertices),
|
||||
"faces": str(self.faces),
|
||||
"nVertices": self.nVertices,
|
||||
"nFaces": self.nFaces,
|
||||
}
|
||||
|
||||
|
||||
def makeSVGedge(e):
|
||||
@ -229,20 +231,16 @@ def makeSVGedge(e):
|
||||
start = curve.FirstParameter()
|
||||
end = curve.LastParameter()
|
||||
|
||||
points = GCPnts_QuasiUniformDeflection(curve,
|
||||
DISCRETIZATION_TOLERANCE,
|
||||
start,
|
||||
end)
|
||||
points = GCPnts_QuasiUniformDeflection(curve, DISCRETIZATION_TOLERANCE, start, end)
|
||||
|
||||
if points.IsDone():
|
||||
point_it = (points.Value(i + 1) for i in
|
||||
range(points.NbPoints()))
|
||||
point_it = (points.Value(i + 1) for i in range(points.NbPoints()))
|
||||
|
||||
p = next(point_it)
|
||||
cs.write('M{},{} '.format(p.X(), p.Y()))
|
||||
cs.write("M{},{} ".format(p.X(), p.Y()))
|
||||
|
||||
for p in point_it:
|
||||
cs.write('L{},{} '.format(p.X(), p.Y()))
|
||||
cs.write("L{},{} ".format(p.X(), p.Y()))
|
||||
|
||||
return cs.getvalue()
|
||||
|
||||
@ -271,7 +269,7 @@ def getSVG(shape, opts=None):
|
||||
Export a shape to SVG
|
||||
"""
|
||||
|
||||
d = {'width': 800, 'height': 240, 'marginLeft': 200, 'marginTop': 20}
|
||||
d = {"width": 800, "height": 240, "marginLeft": 200, "marginTop": 20}
|
||||
|
||||
if opts:
|
||||
d.update(opts)
|
||||
@ -279,17 +277,15 @@ def getSVG(shape, opts=None):
|
||||
# need to guess the scale and the coordinate center
|
||||
uom = guessUnitOfMeasure(shape)
|
||||
|
||||
width = float(d['width'])
|
||||
height = float(d['height'])
|
||||
marginLeft = float(d['marginLeft'])
|
||||
marginTop = float(d['marginTop'])
|
||||
width = float(d["width"])
|
||||
height = float(d["height"])
|
||||
marginLeft = float(d["marginLeft"])
|
||||
marginTop = float(d["marginTop"])
|
||||
|
||||
hlr = HLRBRep_Algo()
|
||||
hlr.Add(shape.wrapped)
|
||||
|
||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(),
|
||||
DEFAULT_DIR)
|
||||
)
|
||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(), DEFAULT_DIR))
|
||||
|
||||
hlr.Projector(projector)
|
||||
hlr.Update()
|
||||
@ -330,8 +326,7 @@ def getSVG(shape, opts=None):
|
||||
# convert to native CQ objects
|
||||
visible = list(map(Shape, visible))
|
||||
hidden = list(map(Shape, hidden))
|
||||
(hiddenPaths, visiblePaths) = getPaths(visible,
|
||||
hidden)
|
||||
(hiddenPaths, visiblePaths) = getPaths(visible, hidden)
|
||||
|
||||
# get bounding box -- these are all in 2-d space
|
||||
bb = Compound.makeCompound(hidden + visible).BoundingBox()
|
||||
@ -340,8 +335,10 @@ def getSVG(shape, opts=None):
|
||||
unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75)
|
||||
|
||||
# compute amount to translate-- move the top left into view
|
||||
(xTranslate, yTranslate) = ((0 - bb.xmin) + marginLeft /
|
||||
unitScale, (0 - bb.ymax) - marginTop / unitScale)
|
||||
(xTranslate, yTranslate) = (
|
||||
(0 - bb.xmin) + marginLeft / unitScale,
|
||||
(0 - bb.ymax) - marginTop / unitScale,
|
||||
)
|
||||
|
||||
# compute paths ( again -- had to strip out freecad crap )
|
||||
hiddenContent = ""
|
||||
@ -356,19 +353,19 @@ def getSVG(shape, opts=None):
|
||||
{
|
||||
"unitScale": str(unitScale),
|
||||
"strokeWidth": str(1.0 / unitScale),
|
||||
"hiddenContent": hiddenContent,
|
||||
"hiddenContent": hiddenContent,
|
||||
"visibleContent": visibleContent,
|
||||
"xTranslate": str(xTranslate),
|
||||
"yTranslate": str(yTranslate),
|
||||
"width": str(width),
|
||||
"height": str(height),
|
||||
"textboxY": str(height - 30),
|
||||
"uom": str(uom)
|
||||
"uom": str(uom),
|
||||
}
|
||||
)
|
||||
# svg = SVG_TEMPLATE % (
|
||||
# {"content": projectedContent}
|
||||
#)
|
||||
# )
|
||||
return svg
|
||||
|
||||
|
||||
@ -380,7 +377,7 @@ def exportSVG(shape, fileName):
|
||||
"""
|
||||
|
||||
svg = getSVG(shape.val())
|
||||
f = open(fileName, 'w')
|
||||
f = open(fileName, "w")
|
||||
f.write(svg)
|
||||
f.close()
|
||||
|
||||
@ -465,4 +462,4 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
||||
PATHTEMPLATE = '\t\t\t<path d="%s" />\n'
|
||||
|
@ -27,16 +27,16 @@ class Vector(object):
|
||||
if len(args) == 3:
|
||||
fV = gp_Vec(*args)
|
||||
elif len(args) == 2:
|
||||
fV = gp_Vec(*args,0)
|
||||
fV = gp_Vec(*args, 0)
|
||||
elif len(args) == 1:
|
||||
if isinstance(args[0], Vector):
|
||||
fV = gp_Vec(args[0].wrapped.XYZ())
|
||||
elif isinstance(args[0], (tuple, list)):
|
||||
arg = args[0]
|
||||
if len(arg)==3:
|
||||
if len(arg) == 3:
|
||||
fV = gp_Vec(*arg)
|
||||
elif len(arg)==2:
|
||||
fV = gp_Vec(*arg,0)
|
||||
elif len(arg) == 2:
|
||||
fV = gp_Vec(*arg, 0)
|
||||
elif isinstance(args[0], (gp_Vec, gp_Pnt, gp_Dir)):
|
||||
fV = gp_Vec(args[0].XYZ())
|
||||
elif isinstance(args[0], gp_XYZ):
|
||||
@ -55,7 +55,7 @@ class Vector(object):
|
||||
return self.wrapped.X()
|
||||
|
||||
@x.setter
|
||||
def x(self,value):
|
||||
def x(self, value):
|
||||
self.wrapped.SetX(value)
|
||||
|
||||
@property
|
||||
@ -63,7 +63,7 @@ class Vector(object):
|
||||
return self.wrapped.Y()
|
||||
|
||||
@y.setter
|
||||
def y(self,value):
|
||||
def y(self, value):
|
||||
self.wrapped.SetY(value)
|
||||
|
||||
@property
|
||||
@ -71,7 +71,7 @@ class Vector(object):
|
||||
return self.wrapped.Z()
|
||||
|
||||
@z.setter
|
||||
def z(self,value):
|
||||
def z(self, value):
|
||||
self.wrapped.SetZ(value)
|
||||
|
||||
@property
|
||||
@ -132,16 +132,13 @@ class Vector(object):
|
||||
return self.wrapped.Angle(v.wrapped)
|
||||
|
||||
def distanceToLine(self):
|
||||
raise NotImplementedError(
|
||||
"Have not needed this yet, but FreeCAD supports it!")
|
||||
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||
|
||||
def projectToLine(self):
|
||||
raise NotImplementedError(
|
||||
"Have not needed this yet, but FreeCAD supports it!")
|
||||
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||
|
||||
def distanceToPlane(self):
|
||||
raise NotImplementedError(
|
||||
"Have not needed this yet, but FreeCAD supports it!")
|
||||
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||
|
||||
def projectToPlane(self, plane):
|
||||
"""
|
||||
@ -154,7 +151,7 @@ class Vector(object):
|
||||
base = plane.origin
|
||||
normal = plane.zDir
|
||||
|
||||
return self-normal*(((self-base).dot(normal))/normal.Length**2)
|
||||
return self - normal * (((self - base).dot(normal)) / normal.Length ** 2)
|
||||
|
||||
def __neg__(self):
|
||||
return self * -1
|
||||
@ -163,18 +160,19 @@ class Vector(object):
|
||||
return self.Length
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||
return "Vector: " + str((self.x, self.y, self.z))
|
||||
|
||||
def __str__(self):
|
||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||
return "Vector: " + str((self.x, self.y, self.z))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.wrapped.IsEqual(other.wrapped, 0.00001, 0.00001)
|
||||
'''
|
||||
|
||||
"""
|
||||
is not implemented in OCC
|
||||
def __ne__(self, other):
|
||||
return self.wrapped.__ne__(other)
|
||||
'''
|
||||
"""
|
||||
|
||||
def toPnt(self):
|
||||
|
||||
@ -222,44 +220,48 @@ class Matrix:
|
||||
elif isinstance(matrix, (list, tuple)):
|
||||
# Validate matrix size & 4x4 last row value
|
||||
valid_sizes = all(
|
||||
(isinstance(row, (list, tuple)) and (len(row) == 4))
|
||||
for row in matrix
|
||||
(isinstance(row, (list, tuple)) and (len(row) == 4)) for row in matrix
|
||||
) and len(matrix) in (3, 4)
|
||||
if not valid_sizes:
|
||||
raise TypeError("Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(matrix))
|
||||
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0,0,0,1)):
|
||||
raise ValueError("Expected the last row to be [0,0,0,1], but got: {!r}".format(matrix[3]))
|
||||
raise TypeError(
|
||||
"Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(
|
||||
matrix
|
||||
)
|
||||
)
|
||||
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0, 0, 0, 1)):
|
||||
raise ValueError(
|
||||
"Expected the last row to be [0,0,0,1], but got: {!r}".format(
|
||||
matrix[3]
|
||||
)
|
||||
)
|
||||
|
||||
# Assign values to matrix
|
||||
self.wrapped = gp_GTrsf()
|
||||
[self.wrapped.SetValue(i+1,j+1,e)
|
||||
for i,row in enumerate(matrix[:3])
|
||||
for j,e in enumerate(row)]
|
||||
[
|
||||
self.wrapped.SetValue(i + 1, j + 1, e)
|
||||
for i, row in enumerate(matrix[:3])
|
||||
for j, e in enumerate(row)
|
||||
]
|
||||
|
||||
else:
|
||||
raise TypeError(
|
||||
"Invalid param to matrix constructor: {}".format(matrix))
|
||||
raise TypeError("Invalid param to matrix constructor: {}".format(matrix))
|
||||
|
||||
def rotateX(self, angle):
|
||||
|
||||
self._rotate(gp.OX_s(),
|
||||
angle)
|
||||
self._rotate(gp.OX_s(), angle)
|
||||
|
||||
def rotateY(self, angle):
|
||||
|
||||
self._rotate(gp.OY_s(),
|
||||
angle)
|
||||
self._rotate(gp.OY_s(), angle)
|
||||
|
||||
def rotateZ(self, angle):
|
||||
|
||||
self._rotate(gp.OZ_s(),
|
||||
angle)
|
||||
self._rotate(gp.OZ_s(), angle)
|
||||
|
||||
def _rotate(self, direction, angle):
|
||||
|
||||
new = gp_Trsf()
|
||||
new.SetRotation(direction,
|
||||
angle)
|
||||
new.SetRotation(direction, angle)
|
||||
|
||||
self.wrapped = self.wrapped * gp_GTrsf(new)
|
||||
|
||||
@ -279,8 +281,9 @@ class Matrix:
|
||||
"""
|
||||
|
||||
trsf = self.wrapped
|
||||
data = [[trsf.Value(i,j) for j in range(1,5)] for i in range(1,4)] + \
|
||||
[[0.,0.,0.,1.]]
|
||||
data = [[trsf.Value(i, j) for j in range(1, 5)] for i in range(1, 4)] + [
|
||||
[0.0, 0.0, 0.0, 1.0]
|
||||
]
|
||||
|
||||
return [data[j][i] for i in range(4) for j in range(4)]
|
||||
|
||||
@ -298,7 +301,7 @@ class Matrix:
|
||||
else:
|
||||
# gp_GTrsf doesn't provide access to the 4th row because it has
|
||||
# an implied value as below:
|
||||
return [0., 0., 0., 1.][c]
|
||||
return [0.0, 0.0, 0.0, 1.0][c]
|
||||
else:
|
||||
raise IndexError("Out of bounds access into 4x4 matrix: {!r}".format(rc))
|
||||
|
||||
@ -352,95 +355,94 @@ class Plane(object):
|
||||
|
||||
namedPlanes = {
|
||||
# origin, xDir, normal
|
||||
'XY': Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||
'YZ': Plane(origin, (0, 1, 0), (1, 0, 0)),
|
||||
'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)),
|
||||
'XZ': Plane(origin, (1, 0, 0), (0, -1, 0)),
|
||||
'YX': Plane(origin, (0, 1, 0), (0, 0, -1)),
|
||||
'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||
'front': Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||
'back': Plane(origin, (-1, 0, 0), (0, 0, -1)),
|
||||
'left': Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||
'right': Plane(origin, (0, 0, -1), (1, 0, 0)),
|
||||
'top': Plane(origin, (1, 0, 0), (0, 1, 0)),
|
||||
'bottom': Plane(origin, (1, 0, 0), (0, -1, 0))
|
||||
"XY": Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||
"YZ": Plane(origin, (0, 1, 0), (1, 0, 0)),
|
||||
"ZX": Plane(origin, (0, 0, 1), (0, 1, 0)),
|
||||
"XZ": Plane(origin, (1, 0, 0), (0, -1, 0)),
|
||||
"YX": Plane(origin, (0, 1, 0), (0, 0, -1)),
|
||||
"ZY": Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||
"front": Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||
"back": Plane(origin, (-1, 0, 0), (0, 0, -1)),
|
||||
"left": Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||
"right": Plane(origin, (0, 0, -1), (1, 0, 0)),
|
||||
"top": Plane(origin, (1, 0, 0), (0, 1, 0)),
|
||||
"bottom": Plane(origin, (1, 0, 0), (0, -1, 0)),
|
||||
}
|
||||
|
||||
try:
|
||||
return namedPlanes[stdName]
|
||||
except KeyError:
|
||||
raise ValueError('Supported names are {}'.format(
|
||||
list(namedPlanes.keys())))
|
||||
raise ValueError("Supported names are {}".format(list(namedPlanes.keys())))
|
||||
|
||||
@classmethod
|
||||
def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('XY', origin)
|
||||
plane = Plane.named("XY", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
||||
plane = Plane.named('YZ', origin)
|
||||
plane = Plane.named("YZ", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||
plane = Plane.named('ZX', origin)
|
||||
plane = Plane.named("ZX", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('XZ', origin)
|
||||
plane = Plane.named("XZ", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
||||
plane = Plane.named('YX', origin)
|
||||
plane = Plane.named("YX", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def ZY(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||
plane = Plane.named('ZY', origin)
|
||||
plane = Plane.named("ZY", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('front', origin)
|
||||
plane = Plane.named("front", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
|
||||
plane = Plane.named('back', origin)
|
||||
plane = Plane.named("back", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||
plane = Plane.named('left', origin)
|
||||
plane = Plane.named("left", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
|
||||
plane = Plane.named('right', origin)
|
||||
plane = Plane.named("right", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('top', origin)
|
||||
plane = Plane.named("top", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('bottom', origin)
|
||||
plane = Plane.named("bottom", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@ -458,12 +460,12 @@ class Plane(object):
|
||||
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
|
||||
"""
|
||||
zDir = Vector(normal)
|
||||
if (zDir.Length == 0.0):
|
||||
raise ValueError('normal should be non null')
|
||||
if zDir.Length == 0.0:
|
||||
raise ValueError("normal should be non null")
|
||||
|
||||
xDir = Vector(xDir)
|
||||
if (xDir.Length == 0.0):
|
||||
raise ValueError('xDir should be non null')
|
||||
if xDir.Length == 0.0:
|
||||
raise ValueError("xDir should be non null")
|
||||
|
||||
self.zDir = zDir.normalized()
|
||||
self._setPlaneDir(xDir)
|
||||
@ -489,7 +491,8 @@ class Plane(object):
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
# TODO is this property rly needed -- why not handle this in the constructor
|
||||
|
||||
# TODO is this property rly needed -- why not handle this in the constructor
|
||||
|
||||
@origin.setter
|
||||
def origin(self, value):
|
||||
@ -545,7 +548,7 @@ class Plane(object):
|
||||
|
||||
pass
|
||||
|
||||
'''
|
||||
"""
|
||||
# TODO: also use a set of points along the wire to test as well.
|
||||
# TODO: would it be more efficient to create objects in the local
|
||||
# coordinate system, and then transform to global
|
||||
@ -562,7 +565,7 @@ class Plane(object):
|
||||
# findOutsideBox actually inspects both ways, here we only want to
|
||||
# know if one is inside the other
|
||||
return bb == BoundBox.findOutsideBox2D(bb, tb)
|
||||
'''
|
||||
"""
|
||||
|
||||
def toLocalCoords(self, obj):
|
||||
"""Project the provided coordinates onto this plane
|
||||
@ -588,7 +591,9 @@ class Plane(object):
|
||||
else:
|
||||
raise ValueError(
|
||||
"Don't know how to convert type {} to local coordinates".format(
|
||||
type(obj)))
|
||||
type(obj)
|
||||
)
|
||||
)
|
||||
|
||||
def toWorldCoords(self, tuplePoint):
|
||||
"""Convert a point in local coordinates to global coordinates
|
||||
@ -619,19 +624,29 @@ class Plane(object):
|
||||
:param rotate: Vector [xDegrees, yDegrees, zDegrees]
|
||||
:return: a copy of this plane rotated as requested.
|
||||
"""
|
||||
# NB: this is not a geometric Vector
|
||||
rotate = Vector(rotate)
|
||||
# Convert to radians.
|
||||
rotate = rotate.multiply(math.pi / 180.0)
|
||||
|
||||
# Compute rotation matrix.
|
||||
m = Matrix()
|
||||
m.rotateX(rotate.x)
|
||||
m.rotateY(rotate.y)
|
||||
m.rotateZ(rotate.z)
|
||||
T1 = gp_Trsf()
|
||||
T1.SetRotation(
|
||||
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.xDir.toTuple())), rotate.x
|
||||
)
|
||||
T2 = gp_Trsf()
|
||||
T2.SetRotation(
|
||||
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.yDir.toTuple())), rotate.y
|
||||
)
|
||||
T3 = gp_Trsf()
|
||||
T3.SetRotation(
|
||||
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.zDir.toTuple())), rotate.z
|
||||
)
|
||||
T = Matrix(gp_GTrsf(T1 * T2 * T3))
|
||||
|
||||
# Compute the new plane.
|
||||
newXdir = self.xDir.transform(m)
|
||||
newZdir = self.zDir.transform(m)
|
||||
newXdir = self.xDir.transform(T)
|
||||
newZdir = self.zDir.transform(T)
|
||||
|
||||
return Plane(self.origin, newXdir, newZdir)
|
||||
|
||||
@ -655,7 +670,7 @@ class Plane(object):
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
'''
|
||||
"""
|
||||
resultWires = []
|
||||
for w in listOfShapes:
|
||||
mirrored = w.transformGeometry(rotationMatrix.wrapped)
|
||||
@ -681,21 +696,19 @@ class Plane(object):
|
||||
|
||||
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
||||
|
||||
return resultWires'''
|
||||
return resultWires"""
|
||||
|
||||
def mirrorInPlane(self, listOfShapes, axis='X'):
|
||||
def mirrorInPlane(self, listOfShapes, axis="X"):
|
||||
|
||||
local_coord_system = gp_Ax3(self.origin.toPnt(),
|
||||
self.zDir.toDir(),
|
||||
self.xDir.toDir())
|
||||
local_coord_system = gp_Ax3(
|
||||
self.origin.toPnt(), self.zDir.toDir(), self.xDir.toDir()
|
||||
)
|
||||
T = gp_Trsf()
|
||||
|
||||
if axis == 'X':
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
||||
local_coord_system.XDirection()))
|
||||
elif axis == 'Y':
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
||||
local_coord_system.YDirection()))
|
||||
if axis == "X":
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.XDirection()))
|
||||
elif axis == "Y":
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.YDirection()))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -731,17 +744,16 @@ class Plane(object):
|
||||
inverseT = gp_Trsf()
|
||||
|
||||
global_coord_system = gp_Ax3()
|
||||
local_coord_system = gp_Ax3(gp_Pnt(*self.origin.toTuple()),
|
||||
gp_Dir(*self.zDir.toTuple()),
|
||||
gp_Dir(*self.xDir.toTuple())
|
||||
)
|
||||
local_coord_system = gp_Ax3(
|
||||
gp_Pnt(*self.origin.toTuple()),
|
||||
gp_Dir(*self.zDir.toTuple()),
|
||||
gp_Dir(*self.xDir.toTuple()),
|
||||
)
|
||||
|
||||
forwardT.SetTransformation(global_coord_system,
|
||||
local_coord_system)
|
||||
forwardT.SetTransformation(global_coord_system, local_coord_system)
|
||||
forward.wrapped = gp_GTrsf(forwardT)
|
||||
|
||||
inverseT.SetTransformation(local_coord_system,
|
||||
global_coord_system)
|
||||
inverseT.SetTransformation(local_coord_system, global_coord_system)
|
||||
inverse.wrapped = gp_GTrsf(inverseT)
|
||||
|
||||
# TODO verify if this is OK
|
||||
@ -751,7 +763,7 @@ class Plane(object):
|
||||
|
||||
|
||||
class BoundBox(object):
|
||||
"""A BoundingBox for an object or set of objects. Wraps the OCP.one"""
|
||||
"""A BoundingBox for an object or set of objects. Wraps the OCP one"""
|
||||
|
||||
def __init__(self, bb):
|
||||
self.wrapped = bb
|
||||
@ -767,11 +779,9 @@ class BoundBox(object):
|
||||
self.zmax = ZMax
|
||||
self.zlen = ZMax - ZMin
|
||||
|
||||
self.center = Vector((XMax + XMin) / 2,
|
||||
(YMax + YMin) / 2,
|
||||
(ZMax + ZMin) / 2)
|
||||
self.center = Vector((XMax + XMin) / 2, (YMax + YMin) / 2, (ZMax + ZMin) / 2)
|
||||
|
||||
self.DiagonalLength = self.wrapped.SquareExtent()**0.5
|
||||
self.DiagonalLength = self.wrapped.SquareExtent() ** 0.5
|
||||
|
||||
def add(self, obj, tol=1e-8):
|
||||
"""Returns a modified (expanded) bounding box
|
||||
@ -810,30 +820,36 @@ class BoundBox(object):
|
||||
the built-in implementation i do not understand.
|
||||
"""
|
||||
|
||||
if (bb1.XMin < bb2.XMin and
|
||||
bb1.XMax > bb2.XMax and
|
||||
bb1.YMin < bb2.YMin and
|
||||
bb1.YMax > bb2.YMax):
|
||||
if (
|
||||
bb1.XMin < bb2.XMin
|
||||
and bb1.XMax > bb2.XMax
|
||||
and bb1.YMin < bb2.YMin
|
||||
and bb1.YMax > bb2.YMax
|
||||
):
|
||||
return bb1
|
||||
|
||||
if (bb2.XMin < bb1.XMin and
|
||||
bb2.XMax > bb1.XMax and
|
||||
bb2.YMin < bb1.YMin and
|
||||
bb2.YMax > bb1.YMax):
|
||||
if (
|
||||
bb2.XMin < bb1.XMin
|
||||
and bb2.XMax > bb1.XMax
|
||||
and bb2.YMin < bb1.YMin
|
||||
and bb2.YMax > bb1.YMax
|
||||
):
|
||||
return bb2
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _fromTopoDS(cls, shape, tol=None, optimal=True):
|
||||
'''
|
||||
"""
|
||||
Constructs a bounding box from a TopoDS_Shape
|
||||
'''
|
||||
"""
|
||||
tol = TOL if tol is None else tol # tol = TOL (by default)
|
||||
bbox = Bnd_Box()
|
||||
|
||||
if optimal:
|
||||
BRepBndLib.AddOptimal_s(shape, bbox) #this is 'exact' but expensive - not yet wrapped by PythonOCC
|
||||
BRepBndLib.AddOptimal_s(
|
||||
shape, bbox
|
||||
) # this is 'exact' but expensive - not yet wrapped by PythonOCC
|
||||
else:
|
||||
mesh = BRepMesh_IncrementalMesh(shape, tol, True)
|
||||
mesh.Perform()
|
||||
@ -844,12 +860,14 @@ class BoundBox(object):
|
||||
|
||||
def isInside(self, b2):
|
||||
"""Is the provided bounding box inside this one?"""
|
||||
if (b2.xmin > self.xmin and
|
||||
b2.ymin > self.ymin and
|
||||
b2.zmin > self.zmin and
|
||||
b2.xmax < self.xmax and
|
||||
b2.ymax < self.ymax and
|
||||
b2.zmax < self.zmax):
|
||||
if (
|
||||
b2.xmin > self.xmin
|
||||
and b2.ymin > self.ymin
|
||||
and b2.zmin > self.zmin
|
||||
and b2.xmax < self.xmax
|
||||
and b2.ymax < self.ymax
|
||||
and b2.zmax < self.zmax
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -41,7 +41,7 @@ def importStep(fileName):
|
||||
if readStatus != OCP.IFSelect.IFSelect_RetDone:
|
||||
raise ValueError("STEP File could not be loaded")
|
||||
for i in range(reader.NbRootsForTransfer()):
|
||||
reader.TransferRoot(i+1)
|
||||
reader.TransferRoot(i + 1)
|
||||
|
||||
occ_shapes = []
|
||||
for i in range(reader.NbShapes()):
|
||||
|
@ -1,103 +1,8 @@
|
||||
from OCC.Display.WebGl.x3dom_renderer import X3DExporter
|
||||
from OCC.Core.gp import gp_Quaternion, gp_Vec
|
||||
from uuid import uuid4
|
||||
from math import tan
|
||||
from xml.etree import ElementTree
|
||||
from IPython.display import SVG
|
||||
|
||||
from .geom import BoundBox
|
||||
from .exporters import toString, ExportTypes
|
||||
|
||||
BOILERPLATE = \
|
||||
'''
|
||||
<link rel='stylesheet' type='text/css' href='http://www.x3dom.org/download/x3dom.css'></link>
|
||||
<div style='height: {height}px; width: 100%;' width='100%' height='{height}px'>
|
||||
<x3d style='height: {height}px; width: 100%;' id='{id}' width='100%' height='{height}px'>
|
||||
<scene>
|
||||
<Viewpoint position='{x},{y},{z}' centerOfRotation='{x0} {y0} {z0}' orientation='{rot}' fieldOfView='{fov}'></Viewpoint>
|
||||
{src}
|
||||
</scene>
|
||||
</x3d>
|
||||
</div>
|
||||
<script>
|
||||
if (document.getElementById('X3DOM_JS_MODULE') == null){{
|
||||
var scr = document.createElement('script');
|
||||
head = document.head || document.getElementsByTagName('head')[0];
|
||||
scr.src = 'http://www.x3dom.org/download/x3dom.js';
|
||||
scr.async = false;
|
||||
scr.id = 'X3DOM_JS_MODULE';
|
||||
scr.onload = function () {{
|
||||
x3dom.reload();
|
||||
}}
|
||||
head.insertBefore(scr, head.lastChild);
|
||||
}}
|
||||
else if (typeof x3dom != 'undefined') {{ //call reload only if x3dom already loaded
|
||||
x3dom.reload();
|
||||
}}
|
||||
|
||||
//document.getElementById('{id}').runtime.fitAll()
|
||||
</script>
|
||||
'''
|
||||
def display(shape):
|
||||
|
||||
#https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
|
||||
#better if else
|
||||
|
||||
ROT = (0.77,0.3,0.55,1.28)
|
||||
ROT = (0.,0,0,1.)
|
||||
FOV = 0.2
|
||||
|
||||
def add_x3d_boilerplate(src, height=400, center=(0,0,0), d=(0,0,15), fov=FOV, rot='{} {} {} {} '.format(*ROT)):
|
||||
|
||||
return BOILERPLATE.format(src=src,
|
||||
id=uuid4(),
|
||||
height=height,
|
||||
x=d[0],
|
||||
y=d[1],
|
||||
z=d[2],
|
||||
x0=center[0],
|
||||
y0=center[1],
|
||||
z0=center[2],
|
||||
fov=fov,
|
||||
rot=rot)
|
||||
|
||||
def x3d_display(shape,
|
||||
vertex_shader=None,
|
||||
fragment_shader=None,
|
||||
export_edges=True,
|
||||
color=(1,1,0),
|
||||
specular_color=(1,1,1),
|
||||
shininess=0.4,
|
||||
transparency=0.4,
|
||||
line_color=(0,0,0),
|
||||
line_width=2.,
|
||||
mesh_quality=.3):
|
||||
|
||||
# Export to XML <Scene> tag
|
||||
exporter = X3DExporter(shape,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
export_edges,
|
||||
color,
|
||||
specular_color,
|
||||
shininess,
|
||||
transparency,
|
||||
line_color,
|
||||
line_width,
|
||||
mesh_quality)
|
||||
|
||||
exporter.compute()
|
||||
x3d_str = exporter.to_x3dfile_string(shape_id=0)
|
||||
xml_et = ElementTree.fromstring(x3d_str)
|
||||
scene_tag = xml_et.find('./Scene')
|
||||
|
||||
# Viewport Parameters
|
||||
bb = BoundBox._fromTopoDS(shape)
|
||||
d = max(bb.xlen,bb.ylen,bb.zlen)
|
||||
c = bb.center
|
||||
|
||||
vec = gp_Vec(0,0,d/1.5/tan(FOV/2))
|
||||
quat = gp_Quaternion(*ROT)
|
||||
vec = quat*(vec) + c.wrapped
|
||||
|
||||
# return boilerplate + Scene
|
||||
return add_x3d_boilerplate(ElementTree.tostring(scene_tag).decode('utf-8'),
|
||||
d=(vec.X(),vec.Y(),vec.Z()),
|
||||
center=(c.x,c.y,c.z))
|
||||
return SVG(toString(shape, ExportTypes.SVG))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,22 @@ import re
|
||||
import math
|
||||
from cadquery import Vector, Edge, Vertex, Face, Solid, Shell, Compound
|
||||
from collections import defaultdict
|
||||
from pyparsing import Literal, Word, nums, Optional, Combine, oneOf, upcaseTokens,\
|
||||
CaselessLiteral, Group, infixNotation, opAssoc, Forward,\
|
||||
ZeroOrMore, Keyword
|
||||
from pyparsing import (
|
||||
Literal,
|
||||
Word,
|
||||
nums,
|
||||
Optional,
|
||||
Combine,
|
||||
oneOf,
|
||||
upcaseTokens,
|
||||
CaselessLiteral,
|
||||
Group,
|
||||
infixNotation,
|
||||
opAssoc,
|
||||
Forward,
|
||||
ZeroOrMore,
|
||||
Keyword,
|
||||
)
|
||||
from functools import reduce
|
||||
|
||||
|
||||
@ -81,7 +94,6 @@ class NearestToPointSelector(Selector):
|
||||
self.pnt = pnt
|
||||
|
||||
def filter(self, objectList):
|
||||
|
||||
def dist(tShape):
|
||||
return tShape.Center().sub(Vector(*self.pnt)).Length
|
||||
# if tShape.ShapeType == 'Vertex':
|
||||
@ -121,15 +133,18 @@ class BoxSelector(Selector):
|
||||
def isInsideBox(p):
|
||||
# using XOR for checking if x/y/z is in between regardless
|
||||
# of order of x/y/z0 and x/y/z1
|
||||
return ((p.x < x0) ^ (p.x < x1)) and \
|
||||
((p.y < y0) ^ (p.y < y1)) and \
|
||||
((p.z < z0) ^ (p.z < z1))
|
||||
return (
|
||||
((p.x < x0) ^ (p.x < x1))
|
||||
and ((p.y < y0) ^ (p.y < y1))
|
||||
and ((p.z < z0) ^ (p.z < z1))
|
||||
)
|
||||
|
||||
for o in objectList:
|
||||
if self.test_boundingbox:
|
||||
bb = o.BoundingBox()
|
||||
if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and \
|
||||
isInsideBox(Vector(bb.xmax, bb.ymax, bb.zmax)):
|
||||
if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and isInsideBox(
|
||||
Vector(bb.xmax, bb.ymax, bb.zmax)
|
||||
):
|
||||
result.append(o)
|
||||
else:
|
||||
if isInsideBox(o.Center()):
|
||||
@ -168,7 +183,9 @@ class BaseDirSelector(Selector):
|
||||
|
||||
if self.test(normal):
|
||||
r.append(o)
|
||||
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'):
|
||||
elif type(o) == Edge and (
|
||||
o.geomType() == "LINE" or o.geomType() == "PLANE"
|
||||
):
|
||||
# an edge is parallel to a direction if its underlying geometry is plane or line
|
||||
tangent = o.tangentAt()
|
||||
if self.test(tangent):
|
||||
@ -247,8 +264,7 @@ class PerpendicularDirSelector(BaseDirSelector):
|
||||
|
||||
def test(self, vec):
|
||||
angle = self.direction.getAngle(vec)
|
||||
r = (abs(angle) < self.TOLERANCE) or (
|
||||
abs(angle - math.pi) < self.TOLERANCE)
|
||||
r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE)
|
||||
return not r
|
||||
|
||||
|
||||
@ -314,17 +330,16 @@ class DirectionMinMaxSelector(Selector):
|
||||
self.TOLERANCE = tolerance
|
||||
|
||||
def filter(self, objectList):
|
||||
|
||||
def distance(tShape):
|
||||
return tShape.Center().dot(self.vector)
|
||||
|
||||
# import OrderedDict
|
||||
from collections import OrderedDict
|
||||
|
||||
# make and distance to object dict
|
||||
objectDict = {distance(el): el for el in objectList}
|
||||
# transform it into an ordered dict
|
||||
objectDict = OrderedDict(sorted(list(objectDict.items()),
|
||||
key=lambda x: x[0]))
|
||||
objectDict = OrderedDict(sorted(list(objectDict.items()), key=lambda x: x[0]))
|
||||
|
||||
# find out the max/min distance
|
||||
if self.directionMax:
|
||||
@ -370,8 +385,9 @@ class DirectionNthSelector(ParallelDirSelector):
|
||||
objectDict[round(distance(el), digits)].append(el)
|
||||
|
||||
# choose the Nth unique rounded distance
|
||||
nth_distance = sorted(list(objectDict.keys()),
|
||||
reverse=not self.directionMax)[self.N]
|
||||
nth_distance = sorted(list(objectDict.keys()), reverse=not self.directionMax)[
|
||||
self.N
|
||||
]
|
||||
|
||||
# map back to original objects and return
|
||||
return objectDict[nth_distance]
|
||||
@ -388,8 +404,9 @@ class BinarySelector(Selector):
|
||||
self.right = right
|
||||
|
||||
def filter(self, objectList):
|
||||
return self.filterResults(self.left.filter(objectList),
|
||||
self.right.filter(objectList))
|
||||
return self.filterResults(
|
||||
self.left.filter(objectList), self.right.filter(objectList)
|
||||
)
|
||||
|
||||
def filterResults(self, r_left, r_right):
|
||||
raise NotImplementedError
|
||||
@ -445,52 +462,56 @@ def _makeGrammar():
|
||||
"""
|
||||
|
||||
# float definition
|
||||
point = Literal('.')
|
||||
plusmin = Literal('+') | Literal('-')
|
||||
point = Literal(".")
|
||||
plusmin = Literal("+") | Literal("-")
|
||||
number = Word(nums)
|
||||
integer = Combine(Optional(plusmin) + number)
|
||||
floatn = Combine(integer + Optional(point + Optional(number)))
|
||||
|
||||
# vector definition
|
||||
lbracket = Literal('(')
|
||||
rbracket = Literal(')')
|
||||
comma = Literal(',')
|
||||
vector = Combine(lbracket + floatn('x') + comma +
|
||||
floatn('y') + comma + floatn('z') + rbracket)
|
||||
lbracket = Literal("(")
|
||||
rbracket = Literal(")")
|
||||
comma = Literal(",")
|
||||
vector = Combine(
|
||||
lbracket + floatn("x") + comma + floatn("y") + comma + floatn("z") + rbracket
|
||||
)
|
||||
|
||||
# direction definition
|
||||
simple_dir = oneOf(['X', 'Y', 'Z', 'XY', 'XZ', 'YZ'])
|
||||
direction = simple_dir('simple_dir') | vector('vector_dir')
|
||||
simple_dir = oneOf(["X", "Y", "Z", "XY", "XZ", "YZ"])
|
||||
direction = simple_dir("simple_dir") | vector("vector_dir")
|
||||
|
||||
# CQ type definition
|
||||
cqtype = oneOf(['Plane', 'Cylinder', 'Sphere', 'Cone', 'Line', 'Circle', 'Arc'],
|
||||
caseless=True)
|
||||
cqtype = oneOf(
|
||||
["Plane", "Cylinder", "Sphere", "Cone", "Line", "Circle", "Arc"], caseless=True
|
||||
)
|
||||
cqtype = cqtype.setParseAction(upcaseTokens)
|
||||
|
||||
# type operator
|
||||
type_op = Literal('%')
|
||||
type_op = Literal("%")
|
||||
|
||||
# direction operator
|
||||
direction_op = oneOf(['>', '<'])
|
||||
direction_op = oneOf([">", "<"])
|
||||
|
||||
# index definition
|
||||
ix_number = Group(Optional('-') + Word(nums))
|
||||
lsqbracket = Literal('[').suppress()
|
||||
rsqbracket = Literal(']').suppress()
|
||||
ix_number = Group(Optional("-") + Word(nums))
|
||||
lsqbracket = Literal("[").suppress()
|
||||
rsqbracket = Literal("]").suppress()
|
||||
|
||||
index = lsqbracket + ix_number('index') + rsqbracket
|
||||
index = lsqbracket + ix_number("index") + rsqbracket
|
||||
|
||||
# other operators
|
||||
other_op = oneOf(['|', '#', '+', '-'])
|
||||
other_op = oneOf(["|", "#", "+", "-"])
|
||||
|
||||
# named view
|
||||
named_view = oneOf(['front', 'back', 'left', 'right', 'top', 'bottom'])
|
||||
named_view = oneOf(["front", "back", "left", "right", "top", "bottom"])
|
||||
|
||||
return direction('only_dir') | \
|
||||
(type_op('type_op') + cqtype('cq_type')) | \
|
||||
(direction_op('dir_op') + direction('dir') + Optional(index)) | \
|
||||
(other_op('other_op') + direction('dir')) | \
|
||||
named_view('named_view')
|
||||
return (
|
||||
direction("only_dir")
|
||||
| (type_op("type_op") + cqtype("cq_type"))
|
||||
| (direction_op("dir_op") + direction("dir") + Optional(index))
|
||||
| (other_op("other_op") + direction("dir"))
|
||||
| named_view("named_view")
|
||||
)
|
||||
|
||||
|
||||
_grammar = _makeGrammar() # make a grammar instance
|
||||
@ -506,33 +527,34 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
|
||||
# define all token to object mappings
|
||||
self.axes = {
|
||||
'X': Vector(1, 0, 0),
|
||||
'Y': Vector(0, 1, 0),
|
||||
'Z': Vector(0, 0, 1),
|
||||
'XY': Vector(1, 1, 0),
|
||||
'YZ': Vector(0, 1, 1),
|
||||
'XZ': Vector(1, 0, 1)
|
||||
"X": Vector(1, 0, 0),
|
||||
"Y": Vector(0, 1, 0),
|
||||
"Z": Vector(0, 0, 1),
|
||||
"XY": Vector(1, 1, 0),
|
||||
"YZ": Vector(0, 1, 1),
|
||||
"XZ": Vector(1, 0, 1),
|
||||
}
|
||||
|
||||
self.namedViews = {
|
||||
'front': (Vector(0, 0, 1), True),
|
||||
'back': (Vector(0, 0, 1), False),
|
||||
'left': (Vector(1, 0, 0), False),
|
||||
'right': (Vector(1, 0, 0), True),
|
||||
'top': (Vector(0, 1, 0), True),
|
||||
'bottom': (Vector(0, 1, 0), False)
|
||||
"front": (Vector(0, 0, 1), True),
|
||||
"back": (Vector(0, 0, 1), False),
|
||||
"left": (Vector(1, 0, 0), False),
|
||||
"right": (Vector(1, 0, 0), True),
|
||||
"top": (Vector(0, 1, 0), True),
|
||||
"bottom": (Vector(0, 1, 0), False),
|
||||
}
|
||||
|
||||
self.operatorMinMax = {
|
||||
'>': True,
|
||||
'<': False,
|
||||
">": True,
|
||||
"<": False,
|
||||
}
|
||||
|
||||
self.operator = {
|
||||
'+': DirectionSelector,
|
||||
'-': lambda v: DirectionSelector(-v),
|
||||
'#': PerpendicularDirSelector,
|
||||
'|': ParallelDirSelector}
|
||||
"+": DirectionSelector,
|
||||
"-": lambda v: DirectionSelector(-v),
|
||||
"#": PerpendicularDirSelector,
|
||||
"|": ParallelDirSelector,
|
||||
}
|
||||
|
||||
self.parseResults = parseResults
|
||||
self.mySelector = self._chooseSelector(parseResults)
|
||||
@ -541,23 +563,25 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
Sets up the underlying filters accordingly
|
||||
"""
|
||||
if 'only_dir' in pr:
|
||||
if "only_dir" in pr:
|
||||
vec = self._getVector(pr)
|
||||
return DirectionSelector(vec)
|
||||
|
||||
elif 'type_op' in pr:
|
||||
elif "type_op" in pr:
|
||||
return TypeSelector(pr.cq_type)
|
||||
|
||||
elif 'dir_op' in pr:
|
||||
elif "dir_op" in pr:
|
||||
vec = self._getVector(pr)
|
||||
minmax = self.operatorMinMax[pr.dir_op]
|
||||
|
||||
if 'index' in pr:
|
||||
return DirectionNthSelector(vec, int(''.join(pr.index.asList())), minmax)
|
||||
if "index" in pr:
|
||||
return DirectionNthSelector(
|
||||
vec, int("".join(pr.index.asList())), minmax
|
||||
)
|
||||
else:
|
||||
return DirectionMinMaxSelector(vec, minmax)
|
||||
|
||||
elif 'other_op' in pr:
|
||||
elif "other_op" in pr:
|
||||
vec = self._getVector(pr)
|
||||
return self.operator[pr.other_op](vec)
|
||||
|
||||
@ -569,7 +593,7 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
Translate parsed vector string into a CQ Vector
|
||||
"""
|
||||
if 'vector_dir' in pr:
|
||||
if "vector_dir" in pr:
|
||||
vec = pr.vector_dir
|
||||
return Vector(float(vec.x), float(vec.y), float(vec.z))
|
||||
else:
|
||||
@ -590,10 +614,10 @@ def _makeExpressionGrammar(atom):
|
||||
"""
|
||||
|
||||
# define operators
|
||||
and_op = Literal('and')
|
||||
or_op = Literal('or')
|
||||
delta_op = oneOf(['exc', 'except'])
|
||||
not_op = Literal('not')
|
||||
and_op = Literal("and")
|
||||
or_op = Literal("or")
|
||||
delta_op = oneOf(["exc", "except"])
|
||||
not_op = Literal("not")
|
||||
|
||||
def atom_callback(res):
|
||||
return _SimpleStringSyntaxSelector(res)
|
||||
@ -622,11 +646,15 @@ def _makeExpressionGrammar(atom):
|
||||
return InverseSelector(right)
|
||||
|
||||
# construct the final grammar and set all the callbacks
|
||||
expr = infixNotation(atom,
|
||||
[(and_op, 2, opAssoc.LEFT, and_callback),
|
||||
(or_op, 2, opAssoc.LEFT, or_callback),
|
||||
(delta_op, 2, opAssoc.LEFT, exc_callback),
|
||||
(not_op, 1, opAssoc.RIGHT, not_callback)])
|
||||
expr = infixNotation(
|
||||
atom,
|
||||
[
|
||||
(and_op, 2, opAssoc.LEFT, and_callback),
|
||||
(or_op, 2, opAssoc.LEFT, or_callback),
|
||||
(delta_op, 2, opAssoc.LEFT, exc_callback),
|
||||
(not_op, 1, opAssoc.RIGHT, not_callback),
|
||||
],
|
||||
)
|
||||
|
||||
return expr
|
||||
|
||||
@ -690,8 +718,7 @@ class StringSyntaxSelector(Selector):
|
||||
Feed the input string through the parser and construct an relevant complex selector object
|
||||
"""
|
||||
self.selectorString = selectorString
|
||||
parse_result = _expression_grammar.parseString(selectorString,
|
||||
parseAll=True)
|
||||
parse_result = _expression_grammar.parseString(selectorString, parseAll=True)
|
||||
self.mySelector = parse_result.asList()[0]
|
||||
|
||||
def filter(self, objectList):
|
||||
|
@ -20,11 +20,12 @@ requirements:
|
||||
- pyparsing 2.*
|
||||
|
||||
test:
|
||||
requires:
|
||||
- pytest
|
||||
source_files:
|
||||
- runtests.py
|
||||
- tests/
|
||||
commands:
|
||||
- python runtests.py
|
||||
- pytest -v
|
||||
|
||||
about:
|
||||
summary: CadQuery fork based on PythonOCC
|
||||
|
@ -54,6 +54,7 @@ All 2-d operations require a **Workplane** object to be created.
|
||||
Workplane.threePointArc
|
||||
Workplane.sagittaArc
|
||||
Workplane.radiusArc
|
||||
Workplane.tangentArcPoint
|
||||
Workplane.rotateAndCopy
|
||||
Workplane.mirrorY
|
||||
Workplane.mirrorX
|
||||
|
146
doc/conf.py
146
doc/conf.py
@ -13,71 +13,77 @@
|
||||
|
||||
import sys, os
|
||||
import os.path
|
||||
#print "working path is %s" % os.getcwd()
|
||||
#sys.path.append("../cadquery")
|
||||
|
||||
# print "working path is %s" % os.getcwd()
|
||||
# sys.path.append("../cadquery")
|
||||
import cadquery
|
||||
|
||||
#settings._target = None
|
||||
# settings._target = None
|
||||
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary','cadquery.cq_directive']
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.autosummary",
|
||||
"cadquery.cq_directive",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'CadQuery'
|
||||
copyright = u'Parametric Products Intellectual Holdings LLC, All Rights Reserved'
|
||||
project = u"CadQuery"
|
||||
copyright = u"Parametric Products Intellectual Holdings LLC, All Rights Reserved"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
version = "1.0"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0.0'
|
||||
release = "1.0.0"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
@ -85,27 +91,27 @@ add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#html_theme = 'timlinux-linfiniti-sphinx'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# html_theme = 'timlinux-linfiniti-sphinx'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {
|
||||
# html_theme_options = {
|
||||
# "headerfont": "'Open Sans',Arial,sans-serif",
|
||||
# #"bodyfont:": "'Open Sans',Arial,sans-serif",
|
||||
# #"headerbg" : "{image: url('/img/bg/body.jpg');color:#000000;}",
|
||||
@ -115,9 +121,9 @@ html_theme = 'sphinx_rtd_theme'
|
||||
## "headercolor1": "#13171A;",
|
||||
# "headercolor2": "#444;",
|
||||
# "headerlinkcolor" : "#13171A;",
|
||||
#}
|
||||
# }
|
||||
|
||||
#agogo options
|
||||
# agogo options
|
||||
"""
|
||||
bodyfont (CSS font family): Font for normal text.
|
||||
headerfont (CSS font family): Font for headings.
|
||||
@ -133,14 +139,14 @@ html_theme = 'sphinx_rtd_theme'
|
||||
textalign (CSS text-align value): Text alignment for the body, default is justify.
|
||||
"""
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = "CadQuery Documentation"
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
@ -149,36 +155,36 @@ html_logo = "_static/cqlogo.png"
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
@ -187,72 +193,66 @@ html_show_sourcelink = False
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'CadQuerydoc'
|
||||
htmlhelp_basename = "CadQuerydoc"
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'CadQuery.tex', u'CadQuery Documentation',
|
||||
u'David Cowden', 'manual'),
|
||||
("index", "CadQuery.tex", u"CadQuery Documentation", u"David Cowden", "manual"),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'cadquery', u'CadQuery Documentation',
|
||||
[u'David Cowden'], 1)
|
||||
]
|
||||
man_pages = [("index", "cadquery", u"CadQuery Documentation", [u"David Cowden"], 1)]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
@ -261,16 +261,22 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'CadQuery', u'CadQuery Documentation',
|
||||
u'David Cowden', 'CadQuery', 'A Fluent CAD api',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
"index",
|
||||
"CadQuery",
|
||||
u"CadQuery Documentation",
|
||||
u"David Cowden",
|
||||
"CadQuery",
|
||||
"A Fluent CAD api",
|
||||
"Miscellaneous",
|
||||
),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
168
doc/examples.rst
168
doc/examples.rst
@ -71,8 +71,8 @@ of a working plane is at the center of the face. The default hole depth is thro
|
||||
center_hole_dia = 22.0
|
||||
|
||||
# Create a box based on the dimensions above and add a 22mm center hole
|
||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
||||
.faces(">Z").workplane().hole(center_hole_dia)
|
||||
result = (cq.Workplane("XY").box(length, height, thickness)
|
||||
.faces(">Z").workplane().hole(center_hole_dia))
|
||||
|
||||
show_object(result)
|
||||
|
||||
@ -121,8 +121,8 @@ closed curve.
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("front").lineTo(2.0, 0).lineTo(2.0, 1.0).threePointArc((1.0, 1.5),(0.0, 1.0))\
|
||||
.close().extrude(0.25)
|
||||
result = (cq.Workplane("front").lineTo(2.0, 0).lineTo(2.0, 1.0).threePointArc((1.0, 1.5),(0.0, 1.0))
|
||||
.close().extrude(0.25))
|
||||
show_object(result)
|
||||
|
||||
|
||||
@ -152,7 +152,7 @@ A new work plane center can be established at any point.
|
||||
result = result.center(1.5, 0.0).rect(0.5, 0.5) # new work center is (1.5, 0.0)
|
||||
|
||||
result = result.center(-1.5, 1.5).circle(0.25) # new work center is ( 0.0, 1.5).
|
||||
#the new center is specified relative to the previous center, not global coordinates!
|
||||
# The new center is specified relative to the previous center, not global coordinates!
|
||||
|
||||
result = result.extrude(0.25)
|
||||
show_object(result)
|
||||
@ -204,8 +204,8 @@ correct for small hole sizes.
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("front").box(3.0, 4.0, 0.25).pushPoints ( [ ( 0,0.75 ),(0, -0.75) ]) \
|
||||
.polygon(6, 1.0).cutThruAll()
|
||||
result = (cq.Workplane("front").box(3.0, 4.0, 0.25).pushPoints ( [ ( 0,0.75 ),(0, -0.75) ])
|
||||
.polygon(6, 1.0).cutThruAll())
|
||||
show_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
@ -460,6 +460,32 @@ This example uses an offset workplane to make a compound object, which is perfec
|
||||
* :py:meth:`Workplane.box`
|
||||
* :py:meth:`Workplane`
|
||||
|
||||
Copying Workplanes
|
||||
--------------------------
|
||||
|
||||
An existing CQ object can copy a workplane from another CQ object.
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = (cq.Workplane("front").circle(1).extrude(10) # make a cylinder
|
||||
# We want to make a second cylinder perpendicular to the first,
|
||||
# but we have no face to base the workplane off
|
||||
.copyWorkplane(
|
||||
# create a temporary object with the required workplane
|
||||
cq.Workplane("right", origin=(-5, 0, 0))
|
||||
).circle(1).extrude(10))
|
||||
show_object(result)
|
||||
|
||||
.. topic:: API References
|
||||
|
||||
.. hlist:
|
||||
:columns: 2
|
||||
|
||||
* :py:meth:`CQ.copyWorkplane` **!**
|
||||
* :py:meth:`Workplane.circle`
|
||||
* :py:meth:`Workplane.extrude`
|
||||
* :py:meth:`Workplane`
|
||||
|
||||
Rotated Workplanes
|
||||
--------------------------
|
||||
|
||||
@ -467,9 +493,9 @@ You can create a rotated work plane by specifying angles of rotation relative to
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").workplane() \
|
||||
.transformed(offset=cq.Vector(0, -1.5, 1.0),rotate=cq.Vector(60, 0, 0)) \
|
||||
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.25)
|
||||
result = (cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").workplane()
|
||||
.transformed(offset=cq.Vector(0, -1.5, 1.0),rotate=cq.Vector(60, 0, 0))
|
||||
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.25))
|
||||
show_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
@ -492,8 +518,8 @@ In the example below, a rectangle is drawn, and its vertices are used to locate
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("front").box(2, 2, 0.5).faces(">Z").workplane() \
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.125 )
|
||||
result = (cq.Workplane("front").box(2, 2, 0.5).faces(">Z").workplane()
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.125 ))
|
||||
show_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
@ -538,8 +564,8 @@ and a circular section.
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").circle(1.5) \
|
||||
.workplane(offset=3.0).rect(0.75, 0.5).loft(combine=True)
|
||||
result = (cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").circle(1.5)
|
||||
.workplane(offset=3.0).rect(0.75, 0.5).loft(combine=True))
|
||||
|
||||
show_object(result)
|
||||
|
||||
@ -563,8 +589,8 @@ Similar to :py:meth:`Workplane.hole` , these functions operate on a list of poin
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane(cq.Plane.XY()).box(4,2, 0.5).faces(">Z").workplane().rect(3.5, 1.5, forConstruction=True)\
|
||||
.vertices().cboreHole(0.125, 0.25, 0.125, depth=None)
|
||||
result = (cq.Workplane(cq.Plane.XY()).box(4,2, 0.5).faces(">Z").workplane().rect(3.5, 1.5, forConstruction=True)
|
||||
.vertices().cboreHole(0.125, 0.25, 0.125, depth=None))
|
||||
|
||||
show_object(result)
|
||||
|
||||
@ -604,6 +630,55 @@ Here we fillet all of the edges of a simple plate.
|
||||
* :py:meth:`Workplane.edges`
|
||||
* :py:meth:`Workplane`
|
||||
|
||||
Tagging objects
|
||||
----------------
|
||||
|
||||
The :py:meth:`CQ.tag` method can be used to tag a particular object in the chain with a string, so that it can be refered to later in the chain.
|
||||
|
||||
The :py:meth:`CQ.workplaneFromTagged` method applies :py:meth:`CQ.copyWorkplane` to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors.
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = (cq.Workplane("XY")
|
||||
# create and tag the base workplane
|
||||
.box(10, 10, 10).faces(">Z").workplane().tag("baseplane")
|
||||
# extrude a cylinder
|
||||
.center(-3, 0).circle(1).extrude(3)
|
||||
# to reselect the base workplane, simply
|
||||
.workplaneFromTagged("baseplane")
|
||||
# extrude a second cylinder
|
||||
.center(3, 0).circle(1).extrude(2))
|
||||
show_object(result)
|
||||
|
||||
|
||||
Tags can also be used with most selectors, including :py:meth:`CQ.vertices`, :py:meth:`CQ.faces`, :py:meth:`CQ.edges`, :py:meth:`CQ.wires`, :py:meth:`CQ.shells`, :py:meth:`CQ.solids` and :py:meth:`CQ.compounds`.
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = (cq.Workplane("XY")
|
||||
# create a triangular prism and tag it
|
||||
.polygon(3, 5).extrude(4).tag("prism")
|
||||
# create a sphere that obscures the prism
|
||||
.sphere(10)
|
||||
# create features based on the prism's faces
|
||||
.faces("<X", tag="prism").workplane().circle(1).cutThruAll()
|
||||
.faces(">X", tag="prism").faces(">Y").workplane().circle(1).cutThruAll())
|
||||
show_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
.. hlist::
|
||||
:columns: 2
|
||||
|
||||
* :py:meth:`CQ.tag` **!**
|
||||
* :py:meth:`CQ.getTagged` **!**
|
||||
* :py:meth:`CQ.workplaneFromTagged` **!**
|
||||
* :py:meth:`Workplane.extrude`
|
||||
* :py:meth:`Workplane.cutThruAll`
|
||||
* :py:meth:`Workplane.circle`
|
||||
* :py:meth:`Workplane.faces`
|
||||
* :py:meth:`Workplane`
|
||||
|
||||
A Parametric Bearing Pillow Block
|
||||
------------------------------------
|
||||
|
||||
@ -614,10 +689,10 @@ with just a few lines of code.
|
||||
|
||||
(length,height,bearing_diam, thickness,padding) = ( 30.0, 40.0, 22.0, 10.0, 8.0)
|
||||
|
||||
result = cq.Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(bearing_diam) \
|
||||
.faces(">Z").workplane() \
|
||||
.rect(length-padding,height-padding,forConstruction=True) \
|
||||
.vertices().cboreHole(2.4, 4.4, 2.1)
|
||||
result = (cq.Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(bearing_diam)
|
||||
.faces(">Z").workplane()
|
||||
.rect(length-padding,height-padding,forConstruction=True)
|
||||
.vertices().cboreHole(2.4, 4.4, 2.1))
|
||||
|
||||
show_object(result)
|
||||
|
||||
@ -665,10 +740,10 @@ ones at 13 lines, but that's very short compared to the pythonOCC version, which
|
||||
(L,w,t) = (20.0, 6.0, 3.0)
|
||||
s = cq.Workplane("XY")
|
||||
|
||||
#draw half the profile of the bottle and extrude it
|
||||
p = s.center(-L/2.0, 0).vLine(w/2.0) \
|
||||
.threePointArc((L/2.0, w/2.0 + t),(L, w/2.0)).vLine(-w/2.0) \
|
||||
.mirrorX().extrude(30.0,True)
|
||||
# Draw half the profile of the bottle and extrude it
|
||||
p = (s.center(-L/2.0, 0).vLine(w/2.0)
|
||||
.threePointArc((L/2.0, w/2.0 + t),(L, w/2.0)).vLine(-w/2.0)
|
||||
.mirrorX().extrude(30.0,True))
|
||||
|
||||
#make the neck
|
||||
p = p.faces(">Z").workplane().circle(3.0).extrude(2.0,True)
|
||||
@ -729,9 +804,10 @@ A Parametric Enclosure
|
||||
oshell = oshell.edges("|Z").fillet(p_sideRadius)
|
||||
|
||||
#inner shell
|
||||
ishell = oshell.faces("<Z").workplane(p_thickness,True)\
|
||||
.rect((p_outerWidth - 2.0* p_thickness),(p_outerLength - 2.0*p_thickness))\
|
||||
ishell = (oshell.faces("<Z").workplane(p_thickness,True)
|
||||
.rect((p_outerWidth - 2.0* p_thickness),(p_outerLength - 2.0*p_thickness))
|
||||
.extrude((p_outerHeight - 2.0*p_thickness),False) #set combine false to produce just the new boss
|
||||
)
|
||||
ishell = ishell.edges("|Z").fillet(p_sideRadius - p_thickness)
|
||||
|
||||
#make the box outer box
|
||||
@ -741,10 +817,10 @@ A Parametric Enclosure
|
||||
POSTWIDTH = (p_outerWidth - 2.0*p_screwpostInset)
|
||||
POSTLENGTH = (p_outerLength -2.0*p_screwpostInset)
|
||||
|
||||
box = box.faces(">Z").workplane(-p_thickness)\
|
||||
.rect(POSTWIDTH,POSTLENGTH,forConstruction=True)\
|
||||
.vertices().circle(p_screwpostOD/2.0).circle(p_screwpostID/2.0)\
|
||||
.extrude((-1.0)*(p_outerHeight + p_lipHeight -p_thickness ),True)
|
||||
box = (box.faces(">Z").workplane(-p_thickness)
|
||||
.rect(POSTWIDTH,POSTLENGTH,forConstruction=True)
|
||||
.vertices().circle(p_screwpostOD/2.0).circle(p_screwpostID/2.0)
|
||||
.extrude((-1.0)*(p_outerHeight + p_lipHeight -p_thickness ),True))
|
||||
|
||||
#split lid into top and bottom parts
|
||||
(lid,bottom) = box.faces(">Z").workplane(-p_thickness -p_lipHeight ).split(keepTop=True,keepBottom=True).all() #splits into two solids
|
||||
@ -835,23 +911,23 @@ regarding the underside of the brick.
|
||||
s = s.faces("<Z").shell(-1.0 * t)
|
||||
|
||||
# make the bumps on the top
|
||||
s = s.faces(">Z").workplane(). \
|
||||
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0) \
|
||||
.extrude(bumpHeight)
|
||||
s = (s.faces(">Z").workplane().
|
||||
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0)
|
||||
.extrude(bumpHeight))
|
||||
|
||||
# add posts on the bottom. posts are different diameter depending on geometry
|
||||
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||
tmp = s.faces("<Z").workplane(invert=True)
|
||||
|
||||
if lbumps > 1 and wbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True). \
|
||||
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t)
|
||||
tmp = (tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True).
|
||||
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t))
|
||||
elif lbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True). \
|
||||
circle(t).extrude(height - t)
|
||||
tmp = (tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True).
|
||||
circle(t).extrude(height - t))
|
||||
elif wbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True). \
|
||||
circle(t).extrude(height - t)
|
||||
tmp = (tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True).
|
||||
circle(t).extrude(height - t))
|
||||
else:
|
||||
tmp = s
|
||||
|
||||
@ -1018,12 +1094,12 @@ Braille Example
|
||||
line_start_pos += Point(0, -cell_geometry.interline)
|
||||
|
||||
r = get_cylinder_radius(cell_geometry)
|
||||
base = base.faces('>Z').vertices('<XY').workplane() \
|
||||
.pushPoints(dot_pos).circle(r) \
|
||||
.extrude(r)
|
||||
base = (base.faces('>Z').vertices('<XY').workplane()
|
||||
.pushPoints(dot_pos).circle(r)
|
||||
.extrude(r))
|
||||
# Make a fillet almost the same radius to get a pseudo spherical cap.
|
||||
base = base.faces('>Z').edges() \
|
||||
.fillet(r - 0.001)
|
||||
base = (base.faces('>Z').edges()
|
||||
.fillet(r - 0.001))
|
||||
hidding_box = cq.Workplane('XY').box(
|
||||
base_width, base_height, base_thickness, centered=(False, False, False))
|
||||
result = hidding_box.union(base)
|
||||
@ -1119,7 +1195,7 @@ This specific examples generates a helical cycloidal gear.
|
||||
return hypocycloid(t,r1,r2)
|
||||
|
||||
# create the gear profile and extrude it
|
||||
result = cq.Workplane('XY').parametricCurve(lambda t: gear(t*2*pi,6,1))\
|
||||
.twistExtrude(15,90).faces('>Z').workplane().circle(2).cutThruAll()
|
||||
result = (cq.Workplane('XY').parametricCurve(lambda t: gear(t*2*pi,6,1))
|
||||
.twistExtrude(15,90).faces('>Z').workplane().circle(2).cutThruAll())
|
||||
|
||||
show_object(result)
|
||||
|
@ -111,12 +111,19 @@ backwards in the stack to get the face as well::
|
||||
You can browse stack access methods here: :ref:`stackMethods`.
|
||||
|
||||
|
||||
.. _chaining:
|
||||
|
||||
Chaining
|
||||
---------------------------
|
||||
|
||||
All CadQuery methods return another CadQuery object, so that you can chain the methods together fluently. Use
|
||||
the core CQ methods to get at the objects that were created.
|
||||
|
||||
Each time a new CadQuery object is produced during these chained calls, it has a ``parent`` attribute that points
|
||||
to the CadQuery object that created it. Several CadQuery methods search this parent chain, for example when searching
|
||||
for the context solid. You can also give a CadQuery object a tag, and further down your chain of CadQuery calls you
|
||||
can refer back to this particular object using it's tag.
|
||||
|
||||
|
||||
The Context Solid
|
||||
---------------------------
|
||||
|
@ -1,14 +1,18 @@
|
||||
name: cadquery
|
||||
channels:
|
||||
- conda-forge
|
||||
- cadquery
|
||||
- conda-forge
|
||||
- defaults
|
||||
dependencies:
|
||||
- python=3.6
|
||||
- pythonocc-core=0.18.2
|
||||
- oce=0.18.2
|
||||
- python>=3.6
|
||||
- cadquery::pythonocc-core
|
||||
- pyparsing
|
||||
- sphinx
|
||||
- sphinx_rtd_theme
|
||||
- black
|
||||
- codecov
|
||||
- pytest
|
||||
- pytest-cov
|
||||
- pip
|
||||
- pip:
|
||||
- "--editable=."
|
||||
# Documentation
|
||||
- sphinx
|
||||
- sphinx_rtd_theme
|
||||
|
@ -1,9 +1,9 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
|
||||
# Create a 3D block based on the dimension variables above.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
|
@ -1,10 +1,10 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
|
||||
# Create a block based on the dimensions above and add a 22mm center hole.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -13,8 +13,13 @@ center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
# 2. The highest (max) Z face is selected and a new workplane is created on it.
|
||||
# 3. The new workplane is used to drill a hole through the block.
|
||||
# 3a. The hole is automatically centered in the workplane.
|
||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
||||
.faces(">Z").workplane().hole(center_hole_dia)
|
||||
result = (
|
||||
cq.Workplane("XY")
|
||||
.box(length, height, thickness)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.hole(center_hole_dia)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,15 +1,15 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
length = 80.0 # Length of the block
|
||||
width = 60.0 # Width of the block
|
||||
height = 100.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
cbore_hole_diameter = 2.4 # Bolt shank/threads clearance hole diameter
|
||||
cbore_inset = 12.0 # How far from the edge the cbored holes are set
|
||||
cbore_diameter = 4.4 # Bolt head pocket hole diameter
|
||||
cbore_depth = 2.1 # Bolt head pocket hole depth
|
||||
length = 80.0 # Length of the block
|
||||
width = 60.0 # Width of the block
|
||||
height = 100.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
cbore_hole_diameter = 2.4 # Bolt shank/threads clearance hole diameter
|
||||
cbore_inset = 12.0 # How far from the edge the cbored holes are set
|
||||
cbore_diameter = 4.4 # Bolt head pocket hole diameter
|
||||
cbore_depth = 2.1 # Bolt head pocket hole depth
|
||||
|
||||
# Create a 3D block based on the dimensions above and add a 22mm center hold
|
||||
# and 4 counterbored holes for bolts
|
||||
@ -26,12 +26,20 @@ cbore_depth = 2.1 # Bolt head pocket hole depth
|
||||
# do not show up in the final displayed geometry.
|
||||
# 6. The vertices of the rectangle (corners) are selected, and a counter-bored
|
||||
# hole is placed at each of the vertices (all 4 of them at once).
|
||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
||||
.faces(">Z").workplane().hole(center_hole_dia) \
|
||||
.faces(">Z").workplane() \
|
||||
.rect(length - cbore_inset, height - cbore_inset, forConstruction=True) \
|
||||
.vertices().cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth) \
|
||||
.edges("|Z").fillet(2.0)
|
||||
result = (
|
||||
cq.Workplane("XY")
|
||||
.box(length, height, thickness)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.hole(center_hole_dia)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.rect(length - cbore_inset, height - cbore_inset, forConstruction=True)
|
||||
.vertices()
|
||||
.cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth)
|
||||
.edges("|Z")
|
||||
.fillet(2.0)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,10 +1,10 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
circle_radius = 50.0 # Radius of the plate
|
||||
thickness = 13.0 # Thickness of the plate
|
||||
rectangle_width = 13.0 # Width of rectangular hole in cylindrical plate
|
||||
rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
|
||||
circle_radius = 50.0 # Radius of the plate
|
||||
thickness = 13.0 # Thickness of the plate
|
||||
rectangle_width = 13.0 # Width of rectangular hole in cylindrical plate
|
||||
rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
|
||||
|
||||
# Extrude a cylindrical plate with a rectangular hole in the middle of it.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -21,9 +21,12 @@ rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
|
||||
# plate with a rectangular hole in the center.
|
||||
# 3a. circle() and rect() could be changed to any other shape to completely
|
||||
# change the resulting plate and/or the hole in it.
|
||||
result = cq.Workplane("front").circle(circle_radius) \
|
||||
.rect(rectangle_width, rectangle_length) \
|
||||
.extrude(thickness)
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.circle(circle_radius)
|
||||
.rect(rectangle_width, rectangle_length)
|
||||
.extrude(thickness)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
width = 2.0 # Overall width of the plate
|
||||
thickness = 0.25 # Thickness of the plate
|
||||
width = 2.0 # Overall width of the plate
|
||||
thickness = 0.25 # Thickness of the plate
|
||||
|
||||
# Extrude a plate outline made of lines and an arc
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -34,12 +34,16 @@ thickness = 0.25 # Thickness of the plate
|
||||
# 7a. Without the close(), the 2D sketch will be left open and the extrude
|
||||
# operation will provide unpredictable results.
|
||||
# 8. The 2D sketch is extruded into a solid object of the specified thickness.
|
||||
result = cq.Workplane("front").lineTo(width, 0) \
|
||||
.lineTo(width, 1.0) \
|
||||
.threePointArc((1.0, 1.5), (0.0, 1.0)) \
|
||||
.sagittaArc((-0.5, 1.0), 0.2) \
|
||||
.radiusArc((-0.7, -0.2), -1.5) \
|
||||
.close().extrude(thickness)
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.lineTo(width, 0)
|
||||
.lineTo(width, 1.0)
|
||||
.threePointArc((1.0, 1.5), (0.0, 1.0))
|
||||
.sagittaArc((-0.5, 1.0), 0.2)
|
||||
.radiusArc((-0.7, -0.2), -1.5)
|
||||
.close()
|
||||
.extrude(thickness)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
circle_radius = 3.0 # The outside radius of the plate
|
||||
thickness = 0.25 # The thickness of the plate
|
||||
circle_radius = 3.0 # The outside radius of the plate
|
||||
thickness = 0.25 # The thickness of the plate
|
||||
|
||||
# Make a plate with two cutouts in it by moving the workplane center point
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
|
@ -1,9 +1,9 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
plate_radius = 2.0 # The radius of the plate that will be extruded
|
||||
plate_radius = 2.0 # The radius of the plate that will be extruded
|
||||
hole_pattern_radius = 0.25 # Radius of circle where the holes will be placed
|
||||
thickness = 0.125 # The thickness of the plate that will be extruded
|
||||
thickness = 0.125 # The thickness of the plate that will be extruded
|
||||
|
||||
# Make a plate with 4 holes in it at various points in a polar arrangement from
|
||||
# the center of the workplane.
|
||||
|
@ -1,11 +1,11 @@
|
||||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
width = 3.0 # The width of the plate
|
||||
height = 4.0 # The height of the plate
|
||||
thickness = 0.25 # The thickness of the plate
|
||||
polygon_sides = 6 # The number of sides that the polygonal holes should have
|
||||
polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
|
||||
width = 3.0 # The width of the plate
|
||||
height = 4.0 # The height of the plate
|
||||
thickness = 0.25 # The thickness of the plate
|
||||
polygon_sides = 6 # The number of sides that the polygonal holes should have
|
||||
polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
|
||||
|
||||
# Create a plate with two polygons cut through it
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
@ -30,10 +30,13 @@ polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
|
||||
# like cutBlind() assume a positive cut direction, but cutThruAll() assumes
|
||||
# instead that the cut is made from a max direction and cuts downward from
|
||||
# that max through all objects.
|
||||
result = cq.Workplane("front").box(width, height, thickness) \
|
||||
.pushPoints([(0, 0.75), (0, -0.75)]) \
|
||||
.polygon(polygon_sides, polygon_dia) \
|
||||
.cutThruAll()
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(width, height, thickness)
|
||||
.pushPoints([(0, 0.75), (0, -0.75)])
|
||||
.polygon(polygon_sides, polygon_dia)
|
||||
.cutThruAll()
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -6,13 +6,13 @@ import cadquery as cq
|
||||
|
||||
# Define the points that the polyline will be drawn to/thru
|
||||
pts = [
|
||||
(W/2.0, H/2.0),
|
||||
(W/2.0, (H/2.0 - t)),
|
||||
(t/2.0, (H/2.0-t)),
|
||||
(t/2.0, (t - H/2.0)),
|
||||
(W/2.0, (t - H/2.0)),
|
||||
(W/2.0, H/-2.0),
|
||||
(0, H/-2.0)
|
||||
(W / 2.0, H / 2.0),
|
||||
(W / 2.0, (H / 2.0 - t)),
|
||||
(t / 2.0, (H / 2.0 - t)),
|
||||
(t / 2.0, (t - H / 2.0)),
|
||||
(W / 2.0, (t - H / 2.0)),
|
||||
(W / 2.0, H / -2.0),
|
||||
(0, H / -2.0),
|
||||
]
|
||||
|
||||
# We generate half of the I-beam outline and then mirror it to create the full
|
||||
@ -30,10 +30,7 @@ pts = [
|
||||
# 3. Only half of the I-beam profile has been drawn so far. That half is
|
||||
# mirrored around the Y-axis to create the complete I-beam profile.
|
||||
# 4. The I-beam profile is extruded to the final length of the beam.
|
||||
result = cq.Workplane("front").moveTo(0, H/2.0) \
|
||||
.polyline(pts) \
|
||||
.mirrorY() \
|
||||
.extrude(L)
|
||||
result = cq.Workplane("front").moveTo(0, H / 2.0).polyline(pts).mirrorY().extrude(L)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -13,7 +13,7 @@ sPnts = [
|
||||
(1.5, 1.0),
|
||||
(1.0, 1.25),
|
||||
(0.5, 1.0),
|
||||
(0, 1.0)
|
||||
(0, 1.0),
|
||||
]
|
||||
|
||||
# 2. Generate our plate with the spline feature and make sure it is a
|
||||
|
@ -13,10 +13,16 @@ import cadquery as cq
|
||||
# 6. Selects the vertices of the for-construction rectangle.
|
||||
# 7. Places holes at the center of each selected vertex.
|
||||
# 7a. Since the workplane is rotated, this results in angled holes in the face.
|
||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z") \
|
||||
.workplane() \
|
||||
.transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0)) \
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.25)
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(4.0, 4.0, 0.25)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0))
|
||||
.rect(1.5, 1.5, forConstruction=True)
|
||||
.vertices()
|
||||
.hole(0.25)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -12,10 +12,15 @@ import cadquery as cq
|
||||
# other geometry.
|
||||
# 6. Selects the vertices of the for-construction rectangle.
|
||||
# 7. Places holes at the center of each selected vertex.
|
||||
result = cq.Workplane("front").box(2, 2, 0.5)\
|
||||
.faces(">Z").workplane() \
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices() \
|
||||
.hole(0.125)
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(2, 2, 0.5)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.rect(1.5, 1.5, forConstruction=True)
|
||||
.vertices()
|
||||
.hole(0.125)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -11,10 +11,15 @@ import cadquery as cq
|
||||
# 5. Creates a workplane 3 mm above the face the circle was drawn on.
|
||||
# 6. Draws a 2D circle on the new, offset workplane.
|
||||
# 7. Creates a loft between the circle and the rectangle.
|
||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z") \
|
||||
.circle(1.5).workplane(offset=3.0) \
|
||||
.rect(0.75, 0.5) \
|
||||
.loft(combine=True)
|
||||
result = (
|
||||
cq.Workplane("front")
|
||||
.box(4.0, 4.0, 0.25)
|
||||
.faces(">Z")
|
||||
.circle(1.5)
|
||||
.workplane(offset=3.0)
|
||||
.rect(0.75, 0.5)
|
||||
.loft(combine=True)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -11,9 +11,15 @@ import cadquery as cq
|
||||
# function.
|
||||
# 5a. When the depth of the counter-sink hole is set to None, the hole will be
|
||||
# cut through.
|
||||
result = cq.Workplane(cq.Plane.XY()).box(4, 2, 0.5).faces(">Z") \
|
||||
.workplane().rect(3.5, 1.5, forConstruction=True) \
|
||||
.vertices().cskHole(0.125, 0.25, 82.0, depth=None)
|
||||
result = (
|
||||
cq.Workplane(cq.Plane.XY())
|
||||
.box(4, 2, 0.5)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.rect(3.5, 1.5, forConstruction=True)
|
||||
.vertices()
|
||||
.cskHole(0.125, 0.25, 82.0, depth=None)
|
||||
)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -9,8 +9,7 @@ import cadquery as cq
|
||||
# that new geometry can be built on.
|
||||
# 4. Draws a 2D circle on the new workplane and then uses it to cut a hole
|
||||
# all the way through the box.
|
||||
c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane() \
|
||||
.circle(0.25).cutThruAll()
|
||||
c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane().circle(0.25).cutThruAll()
|
||||
|
||||
# 5. Selects the face furthest away from the origin in the +Y axis direction.
|
||||
# 6. Creates an offset workplane that is set in the center of the object.
|
||||
|
@ -9,13 +9,13 @@ angle_degrees = 360.0
|
||||
# Revolve a cylinder from a rectangle
|
||||
# Switch comments around in this section to try the revolve operation with different parameters
|
||||
result = cq.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve()
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees)
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5))
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5))
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False)
|
||||
# result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees)
|
||||
# result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5))
|
||||
# result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5))
|
||||
# result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False)
|
||||
|
||||
# Revolve a donut with square walls
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10))
|
||||
# result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10))
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
||||
|
@ -1,11 +1,7 @@
|
||||
import cadquery as cq
|
||||
|
||||
# Points we will use to create spline and polyline paths to sweep over
|
||||
pts = [
|
||||
(0, 1),
|
||||
(1, 2),
|
||||
(2, 4)
|
||||
]
|
||||
pts = [(0, 1), (1, 2), (2, 4)]
|
||||
|
||||
# Spline path generated from our list of points (tuples)
|
||||
path = cq.Workplane("XZ").spline(pts)
|
||||
|
@ -4,37 +4,80 @@ import cadquery as cq
|
||||
path = cq.Workplane("XZ").moveTo(-10, 0).lineTo(10, 0)
|
||||
|
||||
# Sweep a circle from diameter 2.0 to diameter 1.0 to diameter 2.0 along X axis length 10.0 + 10.0
|
||||
defaultSweep = cq.Workplane("YZ").workplane(offset=-10.0).circle(2.0). \
|
||||
workplane(offset=10.0).circle(1.0). \
|
||||
workplane(offset=10.0).circle(2.0).sweep(path, multisection=True)
|
||||
defaultSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-10.0)
|
||||
.circle(2.0)
|
||||
.workplane(offset=10.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=10.0)
|
||||
.circle(2.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
# We can sweep thrue different shapes
|
||||
recttocircleSweep = cq.Workplane("YZ").workplane(offset=-10.0).rect(2.0, 2.0). \
|
||||
workplane(offset=8.0).circle(1.0).workplane(offset=4.0).circle(1.0). \
|
||||
workplane(offset=8.0).rect(2.0, 2.0).sweep(path, multisection=True)
|
||||
recttocircleSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-10.0)
|
||||
.rect(2.0, 2.0)
|
||||
.workplane(offset=8.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=4.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=8.0)
|
||||
.rect(2.0, 2.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
circletorectSweep = cq.Workplane("YZ").workplane(offset=-10.0).circle(1.0). \
|
||||
workplane(offset=7.0).rect(2.0, 2.0).workplane(offset=6.0).rect(2.0, 2.0). \
|
||||
workplane(offset=7.0).circle(1.0).sweep(path, multisection=True)
|
||||
circletorectSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-10.0)
|
||||
.circle(1.0)
|
||||
.workplane(offset=7.0)
|
||||
.rect(2.0, 2.0)
|
||||
.workplane(offset=6.0)
|
||||
.rect(2.0, 2.0)
|
||||
.workplane(offset=7.0)
|
||||
.circle(1.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
|
||||
# Placement of the Shape is important otherwise could produce unexpected shape
|
||||
specialSweep = cq.Workplane("YZ").circle(1.0).workplane(offset=10.0).rect(2.0, 2.0). \
|
||||
sweep(path, multisection=True)
|
||||
specialSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.circle(1.0)
|
||||
.workplane(offset=10.0)
|
||||
.rect(2.0, 2.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
# Switch to an arc for the path : line l=5.0 then half circle r=4.0 then line l=5.0
|
||||
path = cq.Workplane("XZ").moveTo(-5, 4).lineTo(0, 4). \
|
||||
threePointArc((4, 0), (0, -4)).lineTo(-5, -4)
|
||||
path = (
|
||||
cq.Workplane("XZ")
|
||||
.moveTo(-5, 4)
|
||||
.lineTo(0, 4)
|
||||
.threePointArc((4, 0), (0, -4))
|
||||
.lineTo(-5, -4)
|
||||
)
|
||||
|
||||
# Placement of different shapes should follow the path
|
||||
# cylinder r=1.5 along first line
|
||||
# then sweep allong arc from r=1.5 to r=1.0
|
||||
# then cylinder r=1.0 along last line
|
||||
arcSweep = cq.Workplane("YZ").workplane(offset=-5).moveTo(0, 4).circle(1.5). \
|
||||
workplane(offset=5).circle(1.5). \
|
||||
moveTo(0, -8).circle(1.0). \
|
||||
workplane(offset=-5).circle(1.0). \
|
||||
sweep(path, multisection=True)
|
||||
arcSweep = (
|
||||
cq.Workplane("YZ")
|
||||
.workplane(offset=-5)
|
||||
.moveTo(0, 4)
|
||||
.circle(1.5)
|
||||
.workplane(offset=5)
|
||||
.circle(1.5)
|
||||
.moveTo(0, -8)
|
||||
.circle(1.0)
|
||||
.workplane(offset=-5)
|
||||
.circle(1.0)
|
||||
.sweep(path, multisection=True)
|
||||
)
|
||||
|
||||
|
||||
# Translate the resulting solids so that they do not overlap and display them left to right
|
||||
@ -43,5 +86,3 @@ show_object(circletorectSweep.translate((0, 5, 0)))
|
||||
show_object(recttocircleSweep.translate((0, 10, 0)))
|
||||
show_object(specialSweep.translate((0, 15, 0)))
|
||||
show_object(arcSweep.translate((0, -5, 0)))
|
||||
|
||||
|
||||
|
@ -4,8 +4,8 @@ import cadquery as cq
|
||||
#####
|
||||
# Inputs
|
||||
######
|
||||
lbumps = 1 # number of bumps long
|
||||
wbumps = 1 # number of bumps wide
|
||||
lbumps = 1 # number of bumps long
|
||||
wbumps = 1 # number of bumps wide
|
||||
thin = True # True for thin, False for thick
|
||||
|
||||
#
|
||||
@ -22,8 +22,8 @@ else:
|
||||
|
||||
t = (pitch - (2 * clearance) - bumpDiam) / 2.0
|
||||
postDiam = pitch - t # works out to 6.5
|
||||
total_length = lbumps*pitch - 2.0*clearance
|
||||
total_width = wbumps*pitch - 2.0*clearance
|
||||
total_length = lbumps * pitch - 2.0 * clearance
|
||||
total_width = wbumps * pitch - 2.0 * clearance
|
||||
|
||||
# make the base
|
||||
s = cq.Workplane("XY").box(total_length, total_width, height)
|
||||
@ -32,23 +32,37 @@ s = cq.Workplane("XY").box(total_length, total_width, height)
|
||||
s = s.faces("<Z").shell(-1.0 * t)
|
||||
|
||||
# make the bumps on the top
|
||||
s = s.faces(">Z").workplane(). \
|
||||
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0) \
|
||||
s = (
|
||||
s.faces(">Z")
|
||||
.workplane()
|
||||
.rarray(pitch, pitch, lbumps, wbumps, True)
|
||||
.circle(bumpDiam / 2.0)
|
||||
.extrude(bumpHeight)
|
||||
)
|
||||
|
||||
# add posts on the bottom. posts are different diameter depending on geometry
|
||||
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||
tmp = s.faces("<Z").workplane(invert=True)
|
||||
|
||||
if lbumps > 1 and wbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True). \
|
||||
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t)
|
||||
tmp = (
|
||||
tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True)
|
||||
.circle(postDiam / 2.0)
|
||||
.circle(bumpDiam / 2.0)
|
||||
.extrude(height - t)
|
||||
)
|
||||
elif lbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True). \
|
||||
circle(t).extrude(height - t)
|
||||
tmp = (
|
||||
tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True)
|
||||
.circle(t)
|
||||
.extrude(height - t)
|
||||
)
|
||||
elif wbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True). \
|
||||
circle(t).extrude(height - t)
|
||||
tmp = (
|
||||
tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True)
|
||||
.circle(t)
|
||||
.extrude(height - t)
|
||||
)
|
||||
else:
|
||||
tmp = s
|
||||
|
||||
|
175
examples/Ex101_InterpPlate.py
Normal file
175
examples/Ex101_InterpPlate.py
Normal file
@ -0,0 +1,175 @@
|
||||
from math import sin, cos, pi, sqrt
|
||||
import cadquery as cq
|
||||
|
||||
# TEST_1
|
||||
# example from PythonOCC core_geometry_geomplate.py, use of thickness = 0 returns 2D surface.
|
||||
thickness = 0
|
||||
edge_points = [[0.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 10.0, 10.0], [0.0, 0.0, 10.0]]
|
||||
surface_points = [[5.0, 5.0, 5.0]]
|
||||
plate_0 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness)
|
||||
print("plate_0.val().Volume() = ", plate_0.val().Volume())
|
||||
plate_0 = plate_0.translate((0, 6 * 12, 0))
|
||||
show_object(plate_0)
|
||||
|
||||
# EXAMPLE 1
|
||||
# Plate with 5 sides and 2 bumps, one side is not co-planar with the other sides
|
||||
thickness = 0.1
|
||||
edge_points = [
|
||||
[-7.0, -7.0, 0.0],
|
||||
[-3.0, -10.0, 3.0],
|
||||
[7.0, -7.0, 0.0],
|
||||
[7.0, 7.0, 0.0],
|
||||
[-7.0, 7.0, 0.0],
|
||||
]
|
||||
edge_wire = cq.Workplane("XY").polyline(
|
||||
[(-7.0, -7.0), (7.0, -7.0), (7.0, 7.0), (-7.0, 7.0)]
|
||||
)
|
||||
# edge_wire = edge_wire.add(cq.Workplane("YZ").workplane().transformed(offset=cq.Vector(0, 0, -7), rotate=cq.Vector(45, 0, 0)).polyline([(-7.,0.), (3,-3), (7.,0.)]))
|
||||
# In CadQuery Sept-2019 it worked with rotate=cq.Vector(0, 45, 0). In CadQuery Dec-2019 rotate=cq.Vector(45, 0, 0) only closes the wire.
|
||||
edge_wire = edge_wire.add(
|
||||
cq.Workplane("YZ")
|
||||
.workplane()
|
||||
.transformed(offset=cq.Vector(0, 0, -7), rotate=cq.Vector(45, 0, 0))
|
||||
.spline([(-7.0, 0.0), (3, -3), (7.0, 0.0)])
|
||||
)
|
||||
surface_points = [[-3.0, -3.0, -3.0], [3.0, 3.0, 3.0]]
|
||||
plate_1 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||
# plate_1 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness) # list of (x,y,z) points instead of wires for edges
|
||||
print("plate_1.val().Volume() = ", plate_1.val().Volume())
|
||||
show_object(plate_1)
|
||||
|
||||
# EXAMPLE 2
|
||||
# Embossed star, need to change optional parameters to obtain nice looking result.
|
||||
r1 = 3.0
|
||||
r2 = 10.0
|
||||
fn = 6
|
||||
thickness = 0.1
|
||||
edge_points = [
|
||||
[r1 * cos(i * pi / fn), r1 * sin(i * pi / fn)]
|
||||
if i % 2 == 0
|
||||
else [r2 * cos(i * pi / fn), r2 * sin(i * pi / fn)]
|
||||
for i in range(2 * fn + 1)
|
||||
]
|
||||
edge_wire = cq.Workplane("XY").polyline(edge_points)
|
||||
r2 = 4.5
|
||||
surface_points = [
|
||||
[r2 * cos(i * pi / fn), r2 * sin(i * pi / fn), 1.0] for i in range(2 * fn)
|
||||
] + [[0.0, 0.0, -2.0]]
|
||||
plate_2 = cq.Workplane("XY").interpPlate(
|
||||
edge_wire,
|
||||
surface_points,
|
||||
thickness,
|
||||
combine=True,
|
||||
clean=True,
|
||||
degree=3,
|
||||
nbPtsOnCur=15,
|
||||
nbIter=2,
|
||||
anisotropy=False,
|
||||
tol2d=0.00001,
|
||||
tol3d=0.0001,
|
||||
tolAng=0.01,
|
||||
tolCurv=0.1,
|
||||
maxDeg=8,
|
||||
maxSegments=49,
|
||||
)
|
||||
# plate_2 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness, combine=True, clean=True, Degree=3, NbPtsOnCur=15, NbIter=2, Anisotropie=False, Tol2d=0.00001, Tol3d=0.0001, TolAng=0.01, TolCurv=0.1, MaxDeg=8, MaxSegments=49) # list of (x,y,z) points instead of wires for edges
|
||||
print("plate_2.val().Volume() = ", plate_2.val().Volume())
|
||||
plate_2 = plate_2.translate((0, 2 * 12, 0))
|
||||
show_object(plate_2)
|
||||
|
||||
# EXAMPLE 3
|
||||
# Points on hexagonal pattern coordinates, use of pushpoints.
|
||||
r1 = 1.0
|
||||
N = 3
|
||||
ca = cos(30.0 * pi / 180.0)
|
||||
sa = sin(30.0 * pi / 180.0)
|
||||
# EVEN ROWS
|
||||
pts = [
|
||||
(-3.0, -3.0),
|
||||
(-1.267949, -3.0),
|
||||
(0.464102, -3.0),
|
||||
(2.196152, -3.0),
|
||||
(-3.0, 0.0),
|
||||
(-1.267949, 0.0),
|
||||
(0.464102, 0.0),
|
||||
(2.196152, 0.0),
|
||||
(-2.133974, -1.5),
|
||||
(-0.401923, -1.5),
|
||||
(1.330127, -1.5),
|
||||
(3.062178, -1.5),
|
||||
(-2.133975, 1.5),
|
||||
(-0.401924, 1.5),
|
||||
(1.330127, 1.5),
|
||||
(3.062178, 1.5),
|
||||
]
|
||||
# Spike surface
|
||||
thickness = 0.1
|
||||
fn = 6
|
||||
edge_points = [
|
||||
[
|
||||
r1 * cos(i * 2 * pi / fn + 30 * pi / 180),
|
||||
r1 * sin(i * 2 * pi / fn + 30 * pi / 180),
|
||||
]
|
||||
for i in range(fn + 1)
|
||||
]
|
||||
surface_points = [
|
||||
[
|
||||
r1 / 4 * cos(i * 2 * pi / fn + 30 * pi / 180),
|
||||
r1 / 4 * sin(i * 2 * pi / fn + 30 * pi / 180),
|
||||
0.75,
|
||||
]
|
||||
for i in range(fn + 1)
|
||||
] + [[0, 0, 2]]
|
||||
edge_wire = cq.Workplane("XY").polyline(edge_points)
|
||||
plate_3 = (
|
||||
cq.Workplane("XY")
|
||||
.pushPoints(pts)
|
||||
.interpPlate(
|
||||
edge_wire,
|
||||
surface_points,
|
||||
thickness,
|
||||
combine=False,
|
||||
clean=False,
|
||||
degree=2,
|
||||
nbPtsOnCur=20,
|
||||
nbIter=2,
|
||||
anisotropy=False,
|
||||
tol2d=0.00001,
|
||||
tol3d=0.0001,
|
||||
tolAng=0.01,
|
||||
tolCurv=0.1,
|
||||
maxDeg=8,
|
||||
maxSegments=9,
|
||||
)
|
||||
)
|
||||
print("plate_3.val().Volume() = ", plate_3.val().Volume())
|
||||
plate_3 = plate_3.translate((0, 4 * 11, 0))
|
||||
show_object(plate_3)
|
||||
|
||||
# EXAMPLE 4
|
||||
# Gyroïd, all edges are splines on different workplanes.
|
||||
thickness = 0.1
|
||||
edge_points = [
|
||||
[[3.54, 3.54], [1.77, 0.0], [3.54, -3.54]],
|
||||
[[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]],
|
||||
[[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]],
|
||||
[[-3.54, -3.54], [-1.77, 0.0], [-3.54, 3.54]],
|
||||
[[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]],
|
||||
[[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]],
|
||||
]
|
||||
plane_list = ["XZ", "XY", "YZ", "XZ", "YZ", "XY"]
|
||||
offset_list = [-3.54, 3.54, 3.54, 3.54, -3.54, -3.54]
|
||||
edge_wire = (
|
||||
cq.Workplane(plane_list[0]).workplane(offset=-offset_list[0]).spline(edge_points[0])
|
||||
)
|
||||
for i in range(len(edge_points) - 1):
|
||||
edge_wire = edge_wire.add(
|
||||
cq.Workplane(plane_list[i + 1])
|
||||
.workplane(offset=-offset_list[i + 1])
|
||||
.spline(edge_points[i + 1])
|
||||
)
|
||||
surface_points = [[0, 0, 0]]
|
||||
plate_4 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||
print("plate_4.val().Volume() = ", plate_4.val().Volume())
|
||||
plate_4 = plate_4.translate((0, 5 * 12, 0))
|
||||
show_object(plate_4)
|
@ -1,3 +0,0 @@
|
||||
sphinx-rtd-theme==0.1.9
|
||||
travis-sphinx==1.1.0
|
||||
Sphinx==1.3.1
|
22
runtests.py
22
runtests.py
@ -1,22 +0,0 @@
|
||||
import sys
|
||||
from tests import *
|
||||
import cadquery
|
||||
import unittest
|
||||
|
||||
#if you are on python 2.7, you can use -m uniitest discover.
|
||||
#but this is required for python 2.6.6 on windows. FreeCAD0.12 will not load
|
||||
#on py 2.7.x on win
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadObjects.TestCadObjects))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQGI.TestCQGI))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQSelectors.TestCQSelectors))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestImporters.TestImporters))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestJupyter.TestJupyter))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestWorkplanes.TestWorkplanes))
|
||||
|
||||
if __name__ == '__main__':
|
||||
result = unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
sys.exit(not result.wasSuccessful())
|
66
setup.py
66
setup.py
@ -15,44 +15,48 @@ import os
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
#if we are building in travis, use the build number as the sub-minor version
|
||||
version = '0.5-SNAPSHOT'
|
||||
if 'TRAVIS_TAG' in os.environ.keys():
|
||||
version= os.environ['TRAVIS_TAG']
|
||||
# if we are building in travis, use the build number as the sub-minor version
|
||||
version = "0.5-SNAPSHOT"
|
||||
if "TRAVIS_TAG" in os.environ.keys():
|
||||
version = os.environ["TRAVIS_TAG"]
|
||||
|
||||
|
||||
setup(
|
||||
name='cadquery',
|
||||
name="cadquery",
|
||||
version=version,
|
||||
url='https://github.com/dcowden/cadquery',
|
||||
license='Apache Public License 2.0',
|
||||
author='David Cowden',
|
||||
author_email='dave.cowden@gmail.com',
|
||||
description='CadQuery is a parametric scripting language for creating and traversing CAD models',
|
||||
long_description=open('README.md').read(),
|
||||
packages=['cadquery','cadquery.contrib','cadquery.occ_impl','cadquery.plugins','tests'],
|
||||
install_requires=['pyparsing'],
|
||||
url="https://github.com/dcowden/cadquery",
|
||||
license="Apache Public License 2.0",
|
||||
author="David Cowden",
|
||||
author_email="dave.cowden@gmail.com",
|
||||
description="CadQuery is a parametric scripting language for creating and traversing CAD models",
|
||||
long_description=open("README.md").read(),
|
||||
packages=[
|
||||
"cadquery",
|
||||
"cadquery.contrib",
|
||||
"cadquery.occ_impl",
|
||||
"cadquery.plugins",
|
||||
"tests",
|
||||
],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
test_suite='tests',
|
||||
|
||||
platforms="any",
|
||||
test_suite="tests",
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
#'Development Status :: 6 - Mature',
|
||||
#'Development Status :: 7 - Inactive',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Intended Audience :: Science/Research',
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: POSIX',
|
||||
'Operating System :: MacOS',
|
||||
'Operating System :: Unix',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Internet',
|
||||
'Topic :: Scientific/Engineering'
|
||||
]
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: Unix",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Internet",
|
||||
"Topic :: Scientific/Engineering",
|
||||
],
|
||||
)
|
||||
|
@ -1,284 +0,0 @@
|
||||
# system modules
|
||||
import sys
|
||||
import unittest
|
||||
from tests import BaseTest
|
||||
from OCP.gp import gp, gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_XYZ
|
||||
from OCP.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
||||
BRepBuilderAPI_MakeEdge,
|
||||
BRepBuilderAPI_MakeFace)
|
||||
|
||||
from OCP.GC import GC_MakeCircle
|
||||
|
||||
from cadquery import *
|
||||
|
||||
|
||||
class TestCadObjects(BaseTest):
|
||||
|
||||
def _make_circle(self):
|
||||
|
||||
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()),
|
||||
2.)
|
||||
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
||||
|
||||
def testVectorConstructors(self):
|
||||
v1 = Vector(1, 2, 3)
|
||||
v2 = Vector((1, 2, 3))
|
||||
v3 = Vector(gp_Vec(1, 2, 3))
|
||||
v4 = Vector([1,2,3])
|
||||
v5 = Vector(gp_XYZ(1,2,3))
|
||||
|
||||
for v in [v1, v2, v3, v4, v5]:
|
||||
self.assertTupleAlmostEquals((1, 2, 3), v.toTuple(), 4)
|
||||
|
||||
v6 = Vector((1,2))
|
||||
v7 = Vector([1,2])
|
||||
v8 = Vector(1,2)
|
||||
|
||||
for v in [v6, v7, v8]:
|
||||
self.assertTupleAlmostEquals((1, 2, 0), v.toTuple(), 4)
|
||||
|
||||
v9 = Vector()
|
||||
self.assertTupleAlmostEquals((0, 0, 0), v9.toTuple(), 4)
|
||||
|
||||
v9.x = 1.
|
||||
v9.y = 2.
|
||||
v9.z = 3.
|
||||
self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)
|
||||
|
||||
def testVertex(self):
|
||||
"""
|
||||
Tests basic vertex functions
|
||||
"""
|
||||
v = Vertex.makeVertex(1, 1, 1)
|
||||
self.assertEqual(1, v.X)
|
||||
self.assertEqual(Vector, type(v.Center()))
|
||||
|
||||
def testBasicBoundingBox(self):
|
||||
v = Vertex.makeVertex(1, 1, 1)
|
||||
v2 = Vertex.makeVertex(2, 2, 2)
|
||||
self.assertEqual(BoundBox, type(v.BoundingBox()))
|
||||
self.assertEqual(BoundBox, type(v2.BoundingBox()))
|
||||
|
||||
bb1 = v.BoundingBox().add(v2.BoundingBox())
|
||||
|
||||
# OCC uses some approximations
|
||||
self.assertAlmostEqual(bb1.xlen, 1.0, 1)
|
||||
|
||||
def testEdgeWrapperCenter(self):
|
||||
e = self._make_circle()
|
||||
|
||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
|
||||
|
||||
def testEdgeWrapperMakeCircle(self):
|
||||
halfCircleEdge = Edge.makeCircle(radius=10, pnt=(
|
||||
0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180)
|
||||
|
||||
#self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
|
||||
self.assertTupleAlmostEquals(
|
||||
(10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals(
|
||||
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3)
|
||||
|
||||
def testFaceWrapperMakePlane(self):
|
||||
mplane = Face.makePlane(10, 10)
|
||||
|
||||
self.assertTupleAlmostEquals(
|
||||
(0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
|
||||
|
||||
def testCenterOfBoundBox(self):
|
||||
pass
|
||||
|
||||
def testCombinedCenterOfBoundBox(self):
|
||||
pass
|
||||
|
||||
def testCompoundCenter(self):
|
||||
"""
|
||||
Tests whether or not a proper weighted center can be found for a compound
|
||||
"""
|
||||
|
||||
def cylinders(self, radius, height):
|
||||
def _cyl(pnt):
|
||||
# Inner function to build a cylinder
|
||||
return Solid.makeCylinder(radius, height, pnt)
|
||||
|
||||
# Combine all the cylinders into a single compound
|
||||
r = self.eachpoint(_cyl, True).combineSolids()
|
||||
|
||||
return r
|
||||
|
||||
Workplane.cyl = cylinders
|
||||
|
||||
# Now test. here we want weird workplane to see if the objects are transformed right
|
||||
s = Workplane("XY").rect(
|
||||
2.0, 3.0, forConstruction=True).vertices().cyl(0.25, 0.5)
|
||||
|
||||
self.assertEqual(4, len(s.val().Solids()))
|
||||
self.assertTupleAlmostEquals(
|
||||
(0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
||||
|
||||
def testDot(self):
|
||||
v1 = Vector(2, 2, 2)
|
||||
v2 = Vector(1, -1, 1)
|
||||
self.assertEqual(2.0, v1.dot(v2))
|
||||
|
||||
def testVectorAdd(self):
|
||||
result = Vector(1, 2, 0) + Vector(0, 0, 3)
|
||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
|
||||
|
||||
def testVectorOperators(self):
|
||||
result = Vector(1, 1, 1) + Vector(2, 2, 2)
|
||||
self.assertEqual(Vector(3, 3, 3), result)
|
||||
|
||||
result = Vector(1, 2, 3) - Vector(3, 2, 1)
|
||||
self.assertEqual(Vector(-2, 0, 2), result)
|
||||
|
||||
result = Vector(1, 2, 3) * 2
|
||||
self.assertEqual(Vector(2, 4, 6), result)
|
||||
|
||||
result = Vector(2, 4, 6) / 2
|
||||
self.assertEqual(Vector(1, 2, 3), result)
|
||||
|
||||
self.assertEqual(Vector(-1, -1, -1), -Vector(1, 1, 1))
|
||||
|
||||
self.assertEqual(0, abs(Vector(0, 0, 0)))
|
||||
self.assertEqual(1, abs(Vector(1, 0, 0)))
|
||||
self.assertEqual((1+4+9)**0.5, abs(Vector(1, 2, 3)))
|
||||
|
||||
def testVectorEquals(self):
|
||||
a = Vector(1, 2, 3)
|
||||
b = Vector(1, 2, 3)
|
||||
c = Vector(1, 2, 3.000001)
|
||||
self.assertEqual(a, b)
|
||||
self.assertEqual(a, c)
|
||||
|
||||
def testVectorProject(self):
|
||||
"""
|
||||
Test method to project vector to plane.
|
||||
"""
|
||||
decimal_places = 9
|
||||
|
||||
normal = Vector(1, 2, 3)
|
||||
base = Vector(5, 7, 9)
|
||||
x_dir = Vector(1, 0, 0)
|
||||
|
||||
# test passing Plane object
|
||||
point = Vector(10, 11, 12).projectToPlane(Plane(base, x_dir, normal))
|
||||
self.assertTupleAlmostEquals(point.toTuple(), (59/7, 55/7, 51/7),
|
||||
decimal_places)
|
||||
|
||||
def testMatrixCreationAndAccess(self):
|
||||
def matrix_vals(m):
|
||||
return [[m[r,c] for c in range(4)] for r in range(4)]
|
||||
# default constructor creates a 4x4 identity matrix
|
||||
m = Matrix()
|
||||
identity = [[1., 0., 0., 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 1.]]
|
||||
self.assertEqual(identity, matrix_vals(m))
|
||||
|
||||
vals4x4 = [[1., 0., 0., 1.],
|
||||
[0., 1., 0., 2.],
|
||||
[0., 0., 1., 3.],
|
||||
[0., 0., 0., 1.]]
|
||||
vals4x4_tuple = tuple(tuple(r) for r in vals4x4)
|
||||
|
||||
# test constructor with 16-value input
|
||||
m = Matrix(vals4x4)
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
m = Matrix(vals4x4_tuple)
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
|
||||
# test constructor with 12-value input (the last 4 are an implied
|
||||
# [0,0,0,1])
|
||||
m = Matrix(vals4x4[:3])
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
m = Matrix(vals4x4_tuple[:3])
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
|
||||
# Test 16-value input with invalid values for the last 4
|
||||
invalid = [[1., 0., 0., 1.],
|
||||
[0., 1., 0., 2.],
|
||||
[0., 0., 1., 3.],
|
||||
[1., 2., 3., 4.]]
|
||||
with self.assertRaises(ValueError):
|
||||
Matrix(invalid)
|
||||
|
||||
# Test input with invalid size / nested types
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([1,2,3])
|
||||
|
||||
# Invalid sub-type
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([[1, 2, 3, 4], 'abc', [1, 2, 3, 4]])
|
||||
|
||||
# test out-of-bounds access
|
||||
m = Matrix()
|
||||
with self.assertRaises(IndexError):
|
||||
m[0, 4]
|
||||
with self.assertRaises(IndexError):
|
||||
m[4, 0]
|
||||
with self.assertRaises(IndexError):
|
||||
m['ab']
|
||||
|
||||
|
||||
def testTranslate(self):
|
||||
e = Edge.makeCircle(2, (1, 2, 3))
|
||||
e2 = e.translate(Vector(0, 0, 1))
|
||||
|
||||
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
|
||||
|
||||
def testVertices(self):
|
||||
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0),
|
||||
gp_Pnt(1, 1, 0)).Edge())
|
||||
self.assertEqual(2, len(e.Vertices()))
|
||||
|
||||
def testPlaneEqual(self):
|
||||
# default orientation
|
||||
self.assertEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1))
|
||||
)
|
||||
# moved origin
|
||||
self.assertEqual(
|
||||
Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1))
|
||||
)
|
||||
# moved x-axis
|
||||
self.assertEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
|
||||
)
|
||||
# moved z-axis
|
||||
self.assertEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
|
||||
)
|
||||
|
||||
def testPlaneNotEqual(self):
|
||||
# type difference
|
||||
for value in [None, 0, 1, 'abc']:
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
value
|
||||
)
|
||||
# origin difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,1), xDir=(1,0,0), normal=(0,0,1))
|
||||
)
|
||||
# x-axis difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
|
||||
)
|
||||
# z-axis difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
File diff suppressed because it is too large
Load Diff
@ -6,21 +6,23 @@ import os
|
||||
|
||||
|
||||
def readFileAsString(fileName):
|
||||
f = open(fileName, 'r')
|
||||
f = open(fileName, "r")
|
||||
s = f.read()
|
||||
f.close()
|
||||
return s
|
||||
|
||||
|
||||
def writeStringToFile(strToWrite, fileName):
|
||||
f = open(fileName, 'w')
|
||||
f = open(fileName, "w")
|
||||
f.write(strToWrite)
|
||||
f.close()
|
||||
|
||||
|
||||
def makeUnitSquareWire():
|
||||
V = Vector
|
||||
return Wire.makePolygon([V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)])
|
||||
return Wire.makePolygon(
|
||||
[V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)]
|
||||
)
|
||||
|
||||
|
||||
def makeUnitCube():
|
||||
@ -38,25 +40,23 @@ def toTuple(v):
|
||||
elif type(v) == Vector:
|
||||
return v.toTuple()
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"dont know how to convert type %s to tuple" % str(type(v)))
|
||||
raise RuntimeError("dont know how to convert type %s to tuple" % str(type(v)))
|
||||
|
||||
|
||||
class BaseTest(unittest.TestCase):
|
||||
|
||||
def assertTupleAlmostEquals(self, expected, actual, places):
|
||||
for i, j in zip(actual, expected):
|
||||
self.assertAlmostEqual(i, j, places)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'TestCadObjects',
|
||||
'TestCadQuery',
|
||||
'TestCQGI',
|
||||
'TestCQSelectors',
|
||||
'TestCQSelectors',
|
||||
'TestExporters',
|
||||
'TestImporters',
|
||||
'TestJupyter',
|
||||
'TestWorkplanes',
|
||||
"TestCadObjects",
|
||||
"TestCadQuery",
|
||||
"TestCQGI",
|
||||
"TestCQSelectors",
|
||||
"TestCQSelectors",
|
||||
"TestExporters",
|
||||
"TestImporters",
|
||||
"TestJupyter",
|
||||
"TestWorkplanes",
|
||||
]
|
||||
|
406
tests/test_cad_objects.py
Normal file
406
tests/test_cad_objects.py
Normal file
@ -0,0 +1,406 @@
|
||||
# system modules
|
||||
import math
|
||||
import sys
|
||||
import unittest
|
||||
from tests import BaseTest
|
||||
from OCP.gp import gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_Elips, gp, gp_XYZ
|
||||
from OCP.BRepBuilderAPI import (
|
||||
BRepBuilderAPI_MakeVertex,
|
||||
BRepBuilderAPI_MakeEdge,
|
||||
BRepBuilderAPI_MakeFace,
|
||||
)
|
||||
|
||||
from OCP.GC import GC_MakeCircle
|
||||
|
||||
from cadquery import *
|
||||
|
||||
DEG2RAD = 2 * math.pi / 360
|
||||
|
||||
|
||||
class TestCadObjects(BaseTest):
|
||||
def _make_circle(self):
|
||||
|
||||
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 2.0)
|
||||
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
||||
|
||||
def _make_ellipse(self):
|
||||
|
||||
ellipse = gp_Elips(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 4.0, 2.0)
|
||||
return Shape.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge())
|
||||
|
||||
def testVectorConstructors(self):
|
||||
v1 = Vector(1, 2, 3)
|
||||
v2 = Vector((1, 2, 3))
|
||||
v3 = Vector(gp_Vec(1, 2, 3))
|
||||
v4 = Vector([1, 2, 3])
|
||||
v5 = Vector(gp_XYZ(1, 2, 3))
|
||||
|
||||
for v in [v1, v2, v3, v4, v5]:
|
||||
self.assertTupleAlmostEquals((1, 2, 3), v.toTuple(), 4)
|
||||
|
||||
v6 = Vector((1, 2))
|
||||
v7 = Vector([1, 2])
|
||||
v8 = Vector(1, 2)
|
||||
|
||||
for v in [v6, v7, v8]:
|
||||
self.assertTupleAlmostEquals((1, 2, 0), v.toTuple(), 4)
|
||||
|
||||
v9 = Vector()
|
||||
self.assertTupleAlmostEquals((0, 0, 0), v9.toTuple(), 4)
|
||||
|
||||
v9.x = 1.0
|
||||
v9.y = 2.0
|
||||
v9.z = 3.0
|
||||
self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)
|
||||
|
||||
def testVertex(self):
|
||||
"""
|
||||
Tests basic vertex functions
|
||||
"""
|
||||
v = Vertex.makeVertex(1, 1, 1)
|
||||
self.assertEqual(1, v.X)
|
||||
self.assertEqual(Vector, type(v.Center()))
|
||||
|
||||
def testBasicBoundingBox(self):
|
||||
v = Vertex.makeVertex(1, 1, 1)
|
||||
v2 = Vertex.makeVertex(2, 2, 2)
|
||||
self.assertEqual(BoundBox, type(v.BoundingBox()))
|
||||
self.assertEqual(BoundBox, type(v2.BoundingBox()))
|
||||
|
||||
bb1 = v.BoundingBox().add(v2.BoundingBox())
|
||||
|
||||
# OCC uses some approximations
|
||||
self.assertAlmostEqual(bb1.xlen, 1.0, 1)
|
||||
|
||||
def testEdgeWrapperCenter(self):
|
||||
e = self._make_circle()
|
||||
|
||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
|
||||
|
||||
def testEdgeWrapperEllipseCenter(self):
|
||||
e = self._make_ellipse()
|
||||
w = Wire.assembleEdges([e])
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 2.0, 3.0), Face.makeFromWires(w).Center().toTuple(), 3
|
||||
)
|
||||
|
||||
def testEdgeWrapperMakeCircle(self):
|
||||
halfCircleEdge = Edge.makeCircle(
|
||||
radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180
|
||||
)
|
||||
|
||||
# self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
|
||||
self.assertTupleAlmostEquals(
|
||||
(10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3
|
||||
)
|
||||
|
||||
def testEdgeWrapperMakeTangentArc(self):
|
||||
tangent_arc = Edge.makeTangentArc(
|
||||
Vector(1, 1), # starts at 1, 1
|
||||
Vector(0, 1), # tangent at start of arc is in the +y direction
|
||||
Vector(2, 1), # arc curves 180 degrees and ends at 2, 1
|
||||
)
|
||||
self.assertTupleAlmostEquals((1, 1, 0), tangent_arc.startPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals((2, 1, 0), tangent_arc.endPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 1, 0), tangent_arc.tangentAt(locationParam=0).toTuple(), 3
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(1, 0, 0), tangent_arc.tangentAt(locationParam=0.5).toTuple(), 3
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, -1, 0), tangent_arc.tangentAt(locationParam=1).toTuple(), 3
|
||||
)
|
||||
|
||||
def testEdgeWrapperMakeEllipse1(self):
|
||||
# Check x_radius > y_radius
|
||||
x_radius, y_radius = 20, 10
|
||||
angle1, angle2 = -75.0, 90.0
|
||||
arcEllipseEdge = Edge.makeEllipse(
|
||||
x_radius=x_radius,
|
||||
y_radius=y_radius,
|
||||
pnt=(0, 0, 0),
|
||||
dir=(0, 0, 1),
|
||||
angle1=angle1,
|
||||
angle2=angle2,
|
||||
)
|
||||
|
||||
start = (
|
||||
x_radius * math.cos(angle1 * DEG2RAD),
|
||||
y_radius * math.sin(angle1 * DEG2RAD),
|
||||
0.0,
|
||||
)
|
||||
end = (
|
||||
x_radius * math.cos(angle2 * DEG2RAD),
|
||||
y_radius * math.sin(angle2 * DEG2RAD),
|
||||
0.0,
|
||||
)
|
||||
self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)
|
||||
|
||||
def testEdgeWrapperMakeEllipse2(self):
|
||||
# Check x_radius < y_radius
|
||||
x_radius, y_radius = 10, 20
|
||||
angle1, angle2 = 0.0, 45.0
|
||||
arcEllipseEdge = Edge.makeEllipse(
|
||||
x_radius=x_radius,
|
||||
y_radius=y_radius,
|
||||
pnt=(0, 0, 0),
|
||||
dir=(0, 0, 1),
|
||||
angle1=angle1,
|
||||
angle2=angle2,
|
||||
)
|
||||
|
||||
start = (
|
||||
x_radius * math.cos(angle1 * DEG2RAD),
|
||||
y_radius * math.sin(angle1 * DEG2RAD),
|
||||
0.0,
|
||||
)
|
||||
end = (
|
||||
x_radius * math.cos(angle2 * DEG2RAD),
|
||||
y_radius * math.sin(angle2 * DEG2RAD),
|
||||
0.0,
|
||||
)
|
||||
self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)
|
||||
|
||||
def testEdgeWrapperMakeCircleWithEllipse(self):
|
||||
# Check x_radius == y_radius
|
||||
x_radius, y_radius = 20, 20
|
||||
angle1, angle2 = 15.0, 60.0
|
||||
arcEllipseEdge = Edge.makeEllipse(
|
||||
x_radius=x_radius,
|
||||
y_radius=y_radius,
|
||||
pnt=(0, 0, 0),
|
||||
dir=(0, 0, 1),
|
||||
angle1=angle1,
|
||||
angle2=angle2,
|
||||
)
|
||||
|
||||
start = (
|
||||
x_radius * math.cos(angle1 * DEG2RAD),
|
||||
y_radius * math.sin(angle1 * DEG2RAD),
|
||||
0.0,
|
||||
)
|
||||
end = (
|
||||
x_radius * math.cos(angle2 * DEG2RAD),
|
||||
y_radius * math.sin(angle2 * DEG2RAD),
|
||||
0.0,
|
||||
)
|
||||
self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)
|
||||
|
||||
def testFaceWrapperMakePlane(self):
|
||||
mplane = Face.makePlane(10, 10)
|
||||
|
||||
self.assertTupleAlmostEquals((0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
|
||||
|
||||
def testCenterOfBoundBox(self):
|
||||
pass
|
||||
|
||||
def testCombinedCenterOfBoundBox(self):
|
||||
pass
|
||||
|
||||
def testCompoundCenter(self):
|
||||
"""
|
||||
Tests whether or not a proper weighted center can be found for a compound
|
||||
"""
|
||||
|
||||
def cylinders(self, radius, height):
|
||||
def _cyl(pnt):
|
||||
# Inner function to build a cylinder
|
||||
return Solid.makeCylinder(radius, height, pnt)
|
||||
|
||||
# Combine all the cylinders into a single compound
|
||||
r = self.eachpoint(_cyl, True).combineSolids()
|
||||
|
||||
return r
|
||||
|
||||
Workplane.cyl = cylinders
|
||||
|
||||
# Now test. here we want weird workplane to see if the objects are transformed right
|
||||
s = (
|
||||
Workplane("XY")
|
||||
.rect(2.0, 3.0, forConstruction=True)
|
||||
.vertices()
|
||||
.cyl(0.25, 0.5)
|
||||
)
|
||||
|
||||
self.assertEqual(4, len(s.val().Solids()))
|
||||
self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
||||
|
||||
def testDot(self):
|
||||
v1 = Vector(2, 2, 2)
|
||||
v2 = Vector(1, -1, 1)
|
||||
self.assertEqual(2.0, v1.dot(v2))
|
||||
|
||||
def testVectorAdd(self):
|
||||
result = Vector(1, 2, 0) + Vector(0, 0, 3)
|
||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
|
||||
|
||||
def testVectorOperators(self):
|
||||
result = Vector(1, 1, 1) + Vector(2, 2, 2)
|
||||
self.assertEqual(Vector(3, 3, 3), result)
|
||||
|
||||
result = Vector(1, 2, 3) - Vector(3, 2, 1)
|
||||
self.assertEqual(Vector(-2, 0, 2), result)
|
||||
|
||||
result = Vector(1, 2, 3) * 2
|
||||
self.assertEqual(Vector(2, 4, 6), result)
|
||||
|
||||
result = Vector(2, 4, 6) / 2
|
||||
self.assertEqual(Vector(1, 2, 3), result)
|
||||
|
||||
self.assertEqual(Vector(-1, -1, -1), -Vector(1, 1, 1))
|
||||
|
||||
self.assertEqual(0, abs(Vector(0, 0, 0)))
|
||||
self.assertEqual(1, abs(Vector(1, 0, 0)))
|
||||
self.assertEqual((1 + 4 + 9) ** 0.5, abs(Vector(1, 2, 3)))
|
||||
|
||||
def testVectorEquals(self):
|
||||
a = Vector(1, 2, 3)
|
||||
b = Vector(1, 2, 3)
|
||||
c = Vector(1, 2, 3.000001)
|
||||
self.assertEqual(a, b)
|
||||
self.assertEqual(a, c)
|
||||
|
||||
def testVectorProject(self):
|
||||
"""
|
||||
Test method to project vector to plane.
|
||||
"""
|
||||
decimal_places = 9
|
||||
|
||||
normal = Vector(1, 2, 3)
|
||||
base = Vector(5, 7, 9)
|
||||
x_dir = Vector(1, 0, 0)
|
||||
|
||||
# test passing Plane object
|
||||
point = Vector(10, 11, 12).projectToPlane(Plane(base, x_dir, normal))
|
||||
self.assertTupleAlmostEquals(
|
||||
point.toTuple(), (59 / 7, 55 / 7, 51 / 7), decimal_places
|
||||
)
|
||||
|
||||
def testMatrixCreationAndAccess(self):
|
||||
def matrix_vals(m):
|
||||
return [[m[r, c] for c in range(4)] for r in range(4)]
|
||||
|
||||
# default constructor creates a 4x4 identity matrix
|
||||
m = Matrix()
|
||||
identity = [
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
]
|
||||
self.assertEqual(identity, matrix_vals(m))
|
||||
|
||||
vals4x4 = [
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 0.0, 2.0],
|
||||
[0.0, 0.0, 1.0, 3.0],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
]
|
||||
vals4x4_tuple = tuple(tuple(r) for r in vals4x4)
|
||||
|
||||
# test constructor with 16-value input
|
||||
m = Matrix(vals4x4)
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
m = Matrix(vals4x4_tuple)
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
|
||||
# test constructor with 12-value input (the last 4 are an implied
|
||||
# [0,0,0,1])
|
||||
m = Matrix(vals4x4[:3])
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
m = Matrix(vals4x4_tuple[:3])
|
||||
self.assertEqual(vals4x4, matrix_vals(m))
|
||||
|
||||
# Test 16-value input with invalid values for the last 4
|
||||
invalid = [
|
||||
[1.0, 0.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 0.0, 2.0],
|
||||
[0.0, 0.0, 1.0, 3.0],
|
||||
[1.0, 2.0, 3.0, 4.0],
|
||||
]
|
||||
with self.assertRaises(ValueError):
|
||||
Matrix(invalid)
|
||||
|
||||
# Test input with invalid size / nested types
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([1, 2, 3])
|
||||
|
||||
# Invalid sub-type
|
||||
with self.assertRaises(TypeError):
|
||||
Matrix([[1, 2, 3, 4], "abc", [1, 2, 3, 4]])
|
||||
|
||||
# test out-of-bounds access
|
||||
m = Matrix()
|
||||
with self.assertRaises(IndexError):
|
||||
m[0, 4]
|
||||
with self.assertRaises(IndexError):
|
||||
m[4, 0]
|
||||
with self.assertRaises(IndexError):
|
||||
m["ab"]
|
||||
|
||||
def testTranslate(self):
|
||||
e = Edge.makeCircle(2, (1, 2, 3))
|
||||
e2 = e.translate(Vector(0, 0, 1))
|
||||
|
||||
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
|
||||
|
||||
def testVertices(self):
|
||||
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(1, 1, 0)).Edge())
|
||||
self.assertEqual(2, len(e.Vertices()))
|
||||
|
||||
def testPlaneEqual(self):
|
||||
# default orientation
|
||||
self.assertEqual(
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# moved origin
|
||||
self.assertEqual(
|
||||
Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# moved x-axis
|
||||
self.assertEqual(
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# moved z-axis
|
||||
self.assertEqual(
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||
)
|
||||
|
||||
def testPlaneNotEqual(self):
|
||||
# type difference
|
||||
for value in [None, 0, 1, "abc"]:
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)), value
|
||||
)
|
||||
# origin difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# x-axis difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||
)
|
||||
# z-axis difference
|
||||
self.assertNotEqual(
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
3350
tests/test_cadquery.py
Normal file
3350
tests/test_cadquery.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -42,8 +42,9 @@ class TestCQGI(BaseTest):
|
||||
model = cqgi.CQModel(TESTSCRIPT)
|
||||
metadata = model.metadata
|
||||
|
||||
self.assertEqual(set(metadata.parameters.keys()), {
|
||||
'height', 'width', 'a', 'b', 'foo'})
|
||||
self.assertEqual(
|
||||
set(metadata.parameters.keys()), {"height", "width", "a", "b", "foo"}
|
||||
)
|
||||
|
||||
def test_build_with_debug(self):
|
||||
model = cqgi.CQModel(TEST_DEBUG_SCRIPT)
|
||||
@ -51,7 +52,7 @@ class TestCQGI(BaseTest):
|
||||
debugItems = result.debugObjects
|
||||
self.assertTrue(len(debugItems) == 2)
|
||||
self.assertTrue(debugItems[0].shape == "bar")
|
||||
self.assertTrue(debugItems[0].options == {"color": 'yellow'})
|
||||
self.assertTrue(debugItems[0].options == {"color": "yellow"})
|
||||
self.assertTrue(debugItems[1].shape == 2.0)
|
||||
self.assertTrue(debugItems[1].options == {})
|
||||
|
||||
@ -65,7 +66,7 @@ class TestCQGI(BaseTest):
|
||||
|
||||
def test_build_with_different_params(self):
|
||||
model = cqgi.CQModel(TESTSCRIPT)
|
||||
result = model.build({'height': 3.0})
|
||||
result = model.build({"height": 3.0})
|
||||
self.assertTrue(result.results[0].shape == "3.0|3.0|bar|1.0")
|
||||
|
||||
def test_describe_parameters(self):
|
||||
@ -76,9 +77,9 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
model = cqgi.CQModel(script)
|
||||
a_param = model.metadata.parameters['a']
|
||||
a_param = model.metadata.parameters["a"]
|
||||
self.assertTrue(a_param.default_value == 2.0)
|
||||
self.assertTrue(a_param.desc == 'FirstLetter')
|
||||
self.assertTrue(a_param.desc == "FirstLetter")
|
||||
self.assertTrue(a_param.varType == cqgi.NumberParameterType)
|
||||
|
||||
def test_describe_parameter_invalid_doesnt_fail_script(self):
|
||||
@ -89,8 +90,8 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
model = cqgi.CQModel(script)
|
||||
a_param = model.metadata.parameters['a']
|
||||
self.assertTrue(a_param.name == 'a')
|
||||
a_param = model.metadata.parameters["a"]
|
||||
self.assertTrue(a_param.name == "a")
|
||||
|
||||
def test_build_with_exception(self):
|
||||
badscript = textwrap.dedent(
|
||||
@ -115,7 +116,7 @@ class TestCQGI(BaseTest):
|
||||
with self.assertRaises(Exception) as context:
|
||||
model = cqgi.CQModel(badscript)
|
||||
|
||||
self.assertTrue('invalid syntax' in context.exception.args)
|
||||
self.assertTrue("invalid syntax" in context.exception.args)
|
||||
|
||||
def test_that_two_results_are_returned(self):
|
||||
script = textwrap.dedent(
|
||||
@ -140,7 +141,7 @@ class TestCQGI(BaseTest):
|
||||
show_object(h)
|
||||
"""
|
||||
)
|
||||
result = cqgi.parse(script).build({'h': 33.33})
|
||||
result = cqgi.parse(script).build({"h": 33.33})
|
||||
self.assertEqual(result.results[0].shape, "33.33")
|
||||
|
||||
def test_that_assigning_string_to_number_fails(self):
|
||||
@ -150,9 +151,8 @@ class TestCQGI(BaseTest):
|
||||
show_object(h)
|
||||
"""
|
||||
)
|
||||
result = cqgi.parse(script).build({'h': "a string"})
|
||||
self.assertTrue(isinstance(result.exception,
|
||||
cqgi.InvalidParameterError))
|
||||
result = cqgi.parse(script).build({"h": "a string"})
|
||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||
|
||||
def test_that_assigning_unknown_var_fails(self):
|
||||
script = textwrap.dedent(
|
||||
@ -162,9 +162,8 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
|
||||
result = cqgi.parse(script).build({'w': "var is not there"})
|
||||
self.assertTrue(isinstance(result.exception,
|
||||
cqgi.InvalidParameterError))
|
||||
result = cqgi.parse(script).build({"w": "var is not there"})
|
||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||
|
||||
def test_that_cq_objects_are_visible(self):
|
||||
script = textwrap.dedent(
|
||||
@ -198,10 +197,10 @@ class TestCQGI(BaseTest):
|
||||
"""
|
||||
)
|
||||
|
||||
result = cqgi.parse(script).build({'h': False})
|
||||
result = cqgi.parse(script).build({"h": False})
|
||||
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(result.first_result.shape, '*False*')
|
||||
self.assertEqual(result.first_result.shape, "*False*")
|
||||
|
||||
def test_that_only_top_level_vars_are_detected(self):
|
||||
script = textwrap.dedent(
|
@ -12,7 +12,6 @@ from tests import BaseTest
|
||||
|
||||
|
||||
class TestExporters(BaseTest):
|
||||
|
||||
def _exportBox(self, eType, stringsToFind):
|
||||
"""
|
||||
Exports a test object, and then looks for
|
||||
@ -21,31 +20,32 @@ class TestExporters(BaseTest):
|
||||
"""
|
||||
p = Workplane("XY").box(1, 2, 3)
|
||||
|
||||
if eType == exporters.ExportTypes.AMF:
|
||||
if eType == exporters.ExportTypes.AMF:
|
||||
s = io.BytesIO()
|
||||
else:
|
||||
s = io.StringIO()
|
||||
|
||||
exporters.exportShape(p, eType, s, 0.1)
|
||||
|
||||
result = '{}'.format(s.getvalue())
|
||||
result = "{}".format(s.getvalue())
|
||||
|
||||
for q in stringsToFind:
|
||||
self.assertTrue(result.find(q) > -1)
|
||||
return result
|
||||
|
||||
def testSTL(self):
|
||||
self._exportBox(exporters.ExportTypes.STL, ['facet normal'])
|
||||
self._exportBox(exporters.ExportTypes.STL, ["facet normal"])
|
||||
|
||||
def testSVG(self):
|
||||
self._exportBox(exporters.ExportTypes.SVG, ['<svg', '<g transform'])
|
||||
self._exportBox(exporters.ExportTypes.SVG, ["<svg", "<g transform"])
|
||||
|
||||
def testAMF(self):
|
||||
self._exportBox(exporters.ExportTypes.AMF, ['<amf units', '</object>'])
|
||||
self._exportBox(exporters.ExportTypes.AMF, ["<amf units", "</object>"])
|
||||
|
||||
def testSTEP(self):
|
||||
self._exportBox(exporters.ExportTypes.STEP, ['FILE_SCHEMA'])
|
||||
self._exportBox(exporters.ExportTypes.STEP, ["FILE_SCHEMA"])
|
||||
|
||||
def testTJS(self):
|
||||
self._exportBox(exporters.ExportTypes.TJS, [
|
||||
'vertices', 'formatVersion', 'faces'])
|
||||
self._exportBox(
|
||||
exporters.ExportTypes.TJS, ["vertices", "formatVersion", "faces"]
|
||||
)
|
@ -36,12 +36,18 @@ class TestImporters(BaseTest):
|
||||
self.assertTrue(importedShape.val().ShapeType() == "Solid")
|
||||
|
||||
# Check the number of faces and vertices per face to make sure we have a box shape
|
||||
self.assertTrue(importedShape.faces("+X").size() ==
|
||||
1 and importedShape.faces("+X").vertices().size() == 4)
|
||||
self.assertTrue(importedShape.faces("+Y").size() ==
|
||||
1 and importedShape.faces("+Y").vertices().size() == 4)
|
||||
self.assertTrue(importedShape.faces("+Z").size() ==
|
||||
1 and importedShape.faces("+Z").vertices().size() == 4)
|
||||
self.assertTrue(
|
||||
importedShape.faces("+X").size() == 1
|
||||
and importedShape.faces("+X").vertices().size() == 4
|
||||
)
|
||||
self.assertTrue(
|
||||
importedShape.faces("+Y").size() == 1
|
||||
and importedShape.faces("+Y").vertices().size() == 4
|
||||
)
|
||||
self.assertTrue(
|
||||
importedShape.faces("+Z").size() == 1
|
||||
and importedShape.faces("+Z").vertices().size() == 4
|
||||
)
|
||||
|
||||
def testSTEP(self):
|
||||
"""
|
||||
@ -55,7 +61,8 @@ class TestImporters(BaseTest):
|
||||
not segfault.
|
||||
"""
|
||||
tmpfile = OUTDIR + "/badSTEP.step"
|
||||
with open(tmpfile, 'w') as f: f.write("invalid STEP file")
|
||||
with open(tmpfile, "w") as f:
|
||||
f.write("invalid STEP file")
|
||||
with self.assertRaises(ValueError):
|
||||
importers.importShape(importers.ImportTypes.STEP, tmpfile)
|
||||
|
||||
@ -69,6 +76,8 @@ class TestImporters(BaseTest):
|
||||
objs = importers.importShape(importers.ImportTypes.STEP, filename)
|
||||
self.assertEqual(2, len(objs.all()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
unittest.main()
|
@ -2,9 +2,10 @@ from tests import BaseTest
|
||||
|
||||
import cadquery
|
||||
|
||||
|
||||
class TestJupyter(BaseTest):
|
||||
def test_repr_html(self):
|
||||
cube = cadquery.Workplane('XY').box(1, 1, 1)
|
||||
cube = cadquery.Workplane("XY").box(1, 1, 1)
|
||||
shape = cube.val()
|
||||
self.assertIsInstance(shape, cadquery.occ_impl.shapes.Solid)
|
||||
|
@ -1,4 +1,4 @@
|
||||
__author__ = 'dcowden'
|
||||
__author__ = "dcowden"
|
||||
|
||||
"""
|
||||
Tests for CadQuery Selectors
|
||||
@ -20,22 +20,19 @@ from cadquery import selectors
|
||||
|
||||
|
||||
class TestCQSelectors(BaseTest):
|
||||
|
||||
def testWorkplaneCenter(self):
|
||||
"Test Moving workplane center"
|
||||
s = Workplane(Plane.XY())
|
||||
|
||||
# current point and world point should be equal
|
||||
self.assertTupleAlmostEquals(
|
||||
(0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
self.assertTupleAlmostEquals((0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
|
||||
# move origin and confirm center moves
|
||||
s.center(-2.0, -2.0)
|
||||
s = s.center(-2.0, -2.0)
|
||||
|
||||
# current point should be 0,0, but
|
||||
|
||||
self.assertTupleAlmostEquals(
|
||||
(-2.0, -2.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
self.assertTupleAlmostEquals((-2.0, -2.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||
|
||||
def testVertices(self):
|
||||
t = makeUnitSquareWire() # square box
|
||||
@ -43,8 +40,7 @@ class TestCQSelectors(BaseTest):
|
||||
|
||||
self.assertEqual(4, c.vertices().size())
|
||||
self.assertEqual(4, c.edges().size())
|
||||
self.assertEqual(0, c.vertices().edges().size()
|
||||
) # no edges on any vertices
|
||||
self.assertEqual(0, c.vertices().edges().size()) # no edges on any vertices
|
||||
# but selecting all edges still yields all vertices
|
||||
self.assertEqual(4, c.edges().vertices().size())
|
||||
self.assertEqual(1, c.wires().size()) # just one wire
|
||||
@ -71,8 +67,7 @@ class TestCQSelectors(BaseTest):
|
||||
def testFirst(self):
|
||||
c = CQ(makeUnitCube())
|
||||
self.assertEqual(type(c.vertices().first().val()), Vertex)
|
||||
self.assertEqual(
|
||||
type(c.vertices().first().first().first().val()), Vertex)
|
||||
self.assertEqual(type(c.vertices().first().first().first().val()), Vertex)
|
||||
|
||||
def testCompounds(self):
|
||||
c = CQ(makeUnitSquareWire())
|
||||
@ -99,11 +94,11 @@ class TestCQSelectors(BaseTest):
|
||||
def testFaceTypesFilter(self):
|
||||
"Filters by face type"
|
||||
c = CQ(makeUnitCube())
|
||||
self.assertEqual(c.faces().size(), c.faces('%PLANE').size())
|
||||
self.assertEqual(c.faces().size(), c.faces('%plane').size())
|
||||
self.assertEqual(0, c.faces('%sphere').size())
|
||||
self.assertEqual(0, c.faces('%cone').size())
|
||||
self.assertEqual(0, c.faces('%SPHERE').size())
|
||||
self.assertEqual(c.faces().size(), c.faces("%PLANE").size())
|
||||
self.assertEqual(c.faces().size(), c.faces("%plane").size())
|
||||
self.assertEqual(0, c.faces("%sphere").size())
|
||||
self.assertEqual(0, c.faces("%cone").size())
|
||||
self.assertEqual(0, c.faces("%SPHERE").size())
|
||||
|
||||
def testPerpendicularDirFilter(self):
|
||||
c = CQ(makeUnitCube())
|
||||
@ -131,10 +126,12 @@ class TestCQSelectors(BaseTest):
|
||||
# faces parallel to Z axis
|
||||
self.assertEqual(2, c.faces("|Z").size())
|
||||
# TODO: provide short names for ParallelDirSelector
|
||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
||||
Vector((0, 0, 1)))).size()) # same thing as above
|
||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
||||
Vector((0, 0, -1)))).size()) # same thing as above
|
||||
self.assertEqual(
|
||||
2, c.faces(selectors.ParallelDirSelector(Vector((0, 0, 1)))).size()
|
||||
) # same thing as above
|
||||
self.assertEqual(
|
||||
2, c.faces(selectors.ParallelDirSelector(Vector((0, 0, -1)))).size()
|
||||
) # same thing as above
|
||||
|
||||
# just for fun, vertices on faces parallel to z
|
||||
self.assertEqual(8, c.faces("|Z").vertices().size())
|
||||
@ -178,97 +175,96 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertEqual(4, len(el))
|
||||
|
||||
def testNthDistance(self):
|
||||
c = Workplane('XY').pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1)
|
||||
c = Workplane("XY").pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1)
|
||||
|
||||
# 2nd face
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), 1)).val()
|
||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||
|
||||
# 2nd face with inversed selection vector
|
||||
val = c.faces(selectors.DirectionNthSelector(
|
||||
Vector(-1, 0, 0), 1)).val()
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(-1, 0, 0), 1)).val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# 2nd last face
|
||||
val = c.faces(selectors.DirectionNthSelector(
|
||||
Vector(1, 0, 0), -2)).val()
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), -2)).val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# Last face
|
||||
val = c.faces(selectors.DirectionNthSelector(
|
||||
Vector(1, 0, 0), -1)).val()
|
||||
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), -1)).val()
|
||||
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||
|
||||
# check if the selected face if normal to the specified Vector
|
||||
self.assertAlmostEqual(
|
||||
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
self.assertAlmostEqual(val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
|
||||
# repeat the test using string based selector
|
||||
|
||||
# 2nd face
|
||||
val = c.faces('>(1,0,0)[1]').val()
|
||||
val = c.faces(">(1,0,0)[1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||
val = c.faces('>X[1]').val()
|
||||
val = c.faces(">X[1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||
|
||||
# 2nd face with inversed selection vector
|
||||
val = c.faces('>(-1,0,0)[1]').val()
|
||||
val = c.faces(">(-1,0,0)[1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
val = c.faces('<X[1]').val()
|
||||
val = c.faces("<X[1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# 2nd last face
|
||||
val = c.faces('>X[-2]').val()
|
||||
val = c.faces(">X[-2]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||
|
||||
# Last face
|
||||
val = c.faces('>X[-1]').val()
|
||||
val = c.faces(">X[-1]").val()
|
||||
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||
|
||||
# check if the selected face if normal to the specified Vector
|
||||
self.assertAlmostEqual(
|
||||
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
self.assertAlmostEqual(val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||
|
||||
# test selection of multiple faces with the same distance
|
||||
c = Workplane('XY')\
|
||||
.box(1, 4, 1, centered=(False, True, False)).faces('<Z')\
|
||||
.box(2, 2, 2, centered=(True, True, False)).faces('>Z')\
|
||||
c = (
|
||||
Workplane("XY")
|
||||
.box(1, 4, 1, centered=(False, True, False))
|
||||
.faces("<Z")
|
||||
.box(2, 2, 2, centered=(True, True, False))
|
||||
.faces(">Z")
|
||||
.box(1, 1, 1, centered=(True, True, False))
|
||||
)
|
||||
|
||||
# select 2nd from the bottom (NB python indexing is 0-based)
|
||||
vals = c.faces('>Z[1]').vals()
|
||||
vals = c.faces(">Z[1]").vals()
|
||||
self.assertEqual(len(vals), 2)
|
||||
|
||||
val = c.faces('>Z[1]').val()
|
||||
val = c.faces(">Z[1]").val()
|
||||
self.assertAlmostEqual(val.Center().z, 1)
|
||||
|
||||
# do the same but by selecting 3rd from the top
|
||||
vals = c.faces('<Z[2]').vals()
|
||||
vals = c.faces("<Z[2]").vals()
|
||||
self.assertEqual(len(vals), 2)
|
||||
|
||||
val = c.faces('<Z[2]').val()
|
||||
val = c.faces("<Z[2]").val()
|
||||
self.assertAlmostEqual(val.Center().z, 1)
|
||||
|
||||
# do the same but by selecting 2nd last from the bottom
|
||||
vals = c.faces('<Z[-2]').vals()
|
||||
vals = c.faces("<Z[-2]").vals()
|
||||
self.assertEqual(len(vals), 2)
|
||||
|
||||
val = c.faces('<Z[-2]').val()
|
||||
val = c.faces("<Z[-2]").val()
|
||||
self.assertAlmostEqual(val.Center().z, 1)
|
||||
|
||||
# verify that <Z[-1] is equivalent to <Z
|
||||
val1 = c.faces('<Z[-1]').val()
|
||||
val2 = c.faces('<Z').val()
|
||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
||||
val2.Center().toTuple(),
|
||||
3)
|
||||
val1 = c.faces("<Z[-1]").val()
|
||||
val2 = c.faces("<Z").val()
|
||||
self.assertTupleAlmostEquals(
|
||||
val1.Center().toTuple(), val2.Center().toTuple(), 3
|
||||
)
|
||||
|
||||
# verify that >Z[-1] is equivalent to >Z
|
||||
val1 = c.faces('>Z[-1]').val()
|
||||
val2 = c.faces('>Z').val()
|
||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
||||
val2.Center().toTuple(),
|
||||
3)
|
||||
val1 = c.faces(">Z[-1]").val()
|
||||
val2 = c.faces(">Z").val()
|
||||
self.assertTupleAlmostEquals(
|
||||
val1.Center().toTuple(), val2.Center().toTuple(), 3
|
||||
)
|
||||
|
||||
def testNearestTo(self):
|
||||
c = CQ(makeUnitCube())
|
||||
@ -302,7 +298,7 @@ class TestCQSelectors(BaseTest):
|
||||
((0.9, -0.1, -0.1), (1.1, 0.1, 0.1), (1.0, 0.0, 0.0)),
|
||||
((0.9, 0.9, -0.1), (1.1, 1.1, 0.1), (1.0, 1.0, 0.0)),
|
||||
((-0.1, 0.9, -0.1), (0.1, 1.1, 0.1), (0.0, 1.0, 0.0)),
|
||||
((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0))
|
||||
((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0)),
|
||||
]
|
||||
|
||||
for d in test_data_vertices:
|
||||
@ -318,11 +314,13 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
|
||||
|
||||
# test multiple vertices selection
|
||||
vl = c.vertices(selectors.BoxSelector(
|
||||
(-0.1, -0.1, 0.9), (0.1, 1.1, 1.1))).vals()
|
||||
vl = c.vertices(
|
||||
selectors.BoxSelector((-0.1, -0.1, 0.9), (0.1, 1.1, 1.1))
|
||||
).vals()
|
||||
self.assertEqual(2, len(vl))
|
||||
vl = c.vertices(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (0.1, 1.1, 1.1))).vals()
|
||||
vl = c.vertices(
|
||||
selectors.BoxSelector((-0.1, -0.1, -0.1), (0.1, 1.1, 1.1))
|
||||
).vals()
|
||||
self.assertEqual(4, len(vl))
|
||||
|
||||
# test edge selection
|
||||
@ -331,7 +329,7 @@ class TestCQSelectors(BaseTest):
|
||||
((0.4, -0.1, -0.1), (0.6, 0.1, 0.1), (0.5, 0.0, 0.0)),
|
||||
((-0.1, -0.1, 0.4), (0.1, 0.1, 0.6), (0.0, 0.0, 0.5)),
|
||||
((0.9, 0.9, 0.4), (1.1, 1.1, 0.6), (1.0, 1.0, 0.5)),
|
||||
((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0))
|
||||
((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0)),
|
||||
]
|
||||
|
||||
for d in test_data_edges:
|
||||
@ -347,11 +345,9 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
|
||||
|
||||
# test multiple edge selection
|
||||
el = c.edges(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
||||
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
||||
self.assertEqual(2, len(el))
|
||||
el = c.edges(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
||||
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
# test face selection
|
||||
@ -360,7 +356,7 @@ class TestCQSelectors(BaseTest):
|
||||
((0.4, -0.1, 0.4), (0.6, 0.1, 0.6), (0.5, 0.0, 0.5)),
|
||||
((0.9, 0.4, 0.4), (1.1, 0.6, 0.6), (1.0, 0.5, 0.5)),
|
||||
((0.4, 0.4, 0.9), (0.6, 0.6, 1.1), (0.5, 0.5, 1.0)),
|
||||
((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0))
|
||||
((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0)),
|
||||
]
|
||||
|
||||
for d in test_data_faces:
|
||||
@ -376,22 +372,23 @@ class TestCQSelectors(BaseTest):
|
||||
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
|
||||
|
||||
# test multiple face selection
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
||||
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
||||
self.assertEqual(2, len(fl))
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
||||
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
||||
self.assertEqual(3, len(fl))
|
||||
|
||||
# test boundingbox option
|
||||
el = c.edges(selectors.BoxSelector(
|
||||
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals()
|
||||
el = c.edges(
|
||||
selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)
|
||||
).vals()
|
||||
self.assertEqual(1, len(el))
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals()
|
||||
fl = c.faces(
|
||||
selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)
|
||||
).vals()
|
||||
self.assertEqual(0, len(fl))
|
||||
fl = c.faces(selectors.BoxSelector(
|
||||
(-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals()
|
||||
fl = c.faces(
|
||||
selectors.BoxSelector((-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)
|
||||
).vals()
|
||||
self.assertEqual(1, len(fl))
|
||||
|
||||
def testAndSelector(self):
|
||||
@ -400,12 +397,13 @@ class TestCQSelectors(BaseTest):
|
||||
S = selectors.StringSyntaxSelector
|
||||
BS = selectors.BoxSelector
|
||||
|
||||
el = c.edges(selectors.AndSelector(
|
||||
S('|X'), BS((-2, -2, 0.1), (2, 2, 2)))).vals()
|
||||
el = c.edges(
|
||||
selectors.AndSelector(S("|X"), BS((-2, -2, 0.1), (2, 2, 2)))
|
||||
).vals()
|
||||
self.assertEqual(2, len(el))
|
||||
|
||||
# test 'and' (intersection) operator
|
||||
el = c.edges(S('|X') & BS((-2, -2, 0.1), (2, 2, 2))).vals()
|
||||
el = c.edges(S("|X") & BS((-2, -2, 0.1), (2, 2, 2))).vals()
|
||||
self.assertEqual(2, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
@ -455,27 +453,27 @@ class TestCQSelectors(BaseTest):
|
||||
|
||||
S = selectors.StringSyntaxSelector
|
||||
|
||||
fl = c.faces(selectors.InverseSelector(S('>Z'))).vals()
|
||||
fl = c.faces(selectors.InverseSelector(S(">Z"))).vals()
|
||||
self.assertEqual(5, len(fl))
|
||||
el = c.faces('>Z').edges(selectors.InverseSelector(S('>X'))).vals()
|
||||
el = c.faces(">Z").edges(selectors.InverseSelector(S(">X"))).vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
# test invert operator
|
||||
fl = c.faces(-S('>Z')).vals()
|
||||
fl = c.faces(-S(">Z")).vals()
|
||||
self.assertEqual(5, len(fl))
|
||||
el = c.faces('>Z').edges(-S('>X')).vals()
|
||||
el = c.faces(">Z").edges(-S(">X")).vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
# test using extended string syntax
|
||||
fl = c.faces('not >Z').vals()
|
||||
fl = c.faces("not >Z").vals()
|
||||
self.assertEqual(5, len(fl))
|
||||
el = c.faces('>Z').edges('not >X').vals()
|
||||
el = c.faces(">Z").edges("not >X").vals()
|
||||
self.assertEqual(3, len(el))
|
||||
|
||||
def testComplexStringSelector(self):
|
||||
c = CQ(makeUnitCube())
|
||||
|
||||
v = c.vertices('(>X and >Y) or (<X and <Y)').vals()
|
||||
v = c.vertices("(>X and >Y) or (<X and <Y)").vals()
|
||||
self.assertEqual(4, len(v))
|
||||
|
||||
def testFaceCount(self):
|
||||
@ -503,25 +501,27 @@ class TestCQSelectors(BaseTest):
|
||||
|
||||
gram = selectors._expression_grammar
|
||||
|
||||
expressions = ['+X ',
|
||||
'-Y',
|
||||
'|(1,0,0)',
|
||||
'#(1.,1.4114,-0.532)',
|
||||
'%Plane',
|
||||
'>XZ',
|
||||
'<Z[-2]',
|
||||
'>(1,4,55.)[20]',
|
||||
'|XY',
|
||||
'<YZ[0]',
|
||||
'front',
|
||||
'back',
|
||||
'left',
|
||||
'right',
|
||||
'top',
|
||||
'bottom',
|
||||
'not |(1,1,0) and >(0,0,1) or XY except >(1,1,1)[-1]',
|
||||
'(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)',
|
||||
'not ( <X or >X or <Y or >Y )']
|
||||
expressions = [
|
||||
"+X ",
|
||||
"-Y",
|
||||
"|(1,0,0)",
|
||||
"#(1.,1.4114,-0.532)",
|
||||
"%Plane",
|
||||
">XZ",
|
||||
"<Z[-2]",
|
||||
">(1,4,55.)[20]",
|
||||
"|XY",
|
||||
"<YZ[0]",
|
||||
"front",
|
||||
"back",
|
||||
"left",
|
||||
"right",
|
||||
"top",
|
||||
"bottom",
|
||||
"not |(1,1,0) and >(0,0,1) or XY except >(1,1,1)[-1]",
|
||||
"(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)",
|
||||
"not ( <X or >X or <Y or >Y )",
|
||||
]
|
||||
|
||||
for e in expressions:
|
||||
gram.parseString(e, parseAll=True)
|
@ -16,65 +16,70 @@ zInvAxis_ = Vector(0, 0, -1)
|
||||
|
||||
|
||||
class TestWorkplanes(BaseTest):
|
||||
|
||||
def testYZPlaneOrigins(self):
|
||||
# xy plane-- with origin at x=0.25
|
||||
base = Vector(0.25, 0, 0)
|
||||
p = Plane(base, Vector(0, 1, 0), Vector(1, 0, 0))
|
||||
|
||||
# origin is always (0,0,0) in local coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
|
||||
#(0,0,0) is always the original base in global coordinates
|
||||
# (0,0,0) is always the original base in global coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
base.toTuple(), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||
base.toTuple(), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testXYPlaneOrigins(self):
|
||||
base = Vector(0, 0, 0.25)
|
||||
p = Plane(base, Vector(1, 0, 0), Vector(0, 0, 1))
|
||||
|
||||
# origin is always (0,0,0) in local coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
|
||||
#(0,0,0) is always the original base in global coordinates
|
||||
# (0,0,0) is always the original base in global coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testXZPlaneOrigins(self):
|
||||
base = Vector(0, 0.25, 0)
|
||||
p = Plane(base, Vector(0, 0, 1), Vector(0, 1, 0))
|
||||
|
||||
#(0,0,0) is always the original base in global coordinates
|
||||
# (0,0,0) is always the original base in global coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
# origin is always (0,0,0) in local coordinates
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||
|
||||
def testPlaneBasics(self):
|
||||
p = Plane.XY()
|
||||
# local to world
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0, 0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
||||
(1.0, 1.0, 0), p.toWorldCoords((1, 1)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(-1.0, -1.0, 0), p.toWorldCoords((-1, -1)).toTuple(), 2)
|
||||
(-1.0, -1.0, 0), p.toWorldCoords((-1, -1)).toTuple(), 2
|
||||
)
|
||||
|
||||
# world to local
|
||||
self.assertTupleAlmostEquals(
|
||||
(-1.0, -1.0), p.toLocalCoords(Vector(-1, -1, 0)).toTuple(), 2)
|
||||
(-1.0, -1.0), p.toLocalCoords(Vector(-1, -1, 0)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 1, 0)).toTuple(), 2)
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 1, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
p = Plane.YZ()
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 1.0, 1.0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
||||
(0, 1.0, 1.0), p.toWorldCoords((1, 1)).toTuple(), 2
|
||||
)
|
||||
|
||||
# world to local
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(0, 1, 1)).toTuple(), 2)
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(0, 1, 1)).toTuple(), 2
|
||||
)
|
||||
|
||||
p = Plane.XZ()
|
||||
r = p.toWorldCoords((1, 1)).toTuple()
|
||||
@ -82,62 +87,68 @@ class TestWorkplanes(BaseTest):
|
||||
|
||||
# world to local
|
||||
self.assertTupleAlmostEquals(
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 0, 1)).toTuple(), 2)
|
||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 0, 1)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testOffsetPlanes(self):
|
||||
"Tests that a plane offset from the origin works ok too"
|
||||
p = Plane.XY(origin=(10.0, 10.0, 0))
|
||||
|
||||
self.assertTupleAlmostEquals(
|
||||
(11.0, 11.0, 0.0), p.toWorldCoords((1.0, 1.0)).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((2.0, 2.0), p.toLocalCoords(
|
||||
Vector(12.0, 12.0, 0)).toTuple(), 2)
|
||||
(11.0, 11.0, 0.0), p.toWorldCoords((1.0, 1.0)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(2.0, 2.0), p.toLocalCoords(Vector(12.0, 12.0, 0)).toTuple(), 2
|
||||
)
|
||||
|
||||
# TODO test these offsets in the other dimensions too
|
||||
p = Plane.YZ(origin=(0, 2, 2))
|
||||
self.assertTupleAlmostEquals(
|
||||
(0.0, 5.0, 5.0), p.toWorldCoords((3.0, 3.0)).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals((10, 10.0, 0.0), p.toLocalCoords(
|
||||
Vector(0.0, 12.0, 12.0)).toTuple(), 2)
|
||||
(0.0, 5.0, 5.0), p.toWorldCoords((3.0, 3.0)).toTuple(), 2
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(10, 10.0, 0.0), p.toLocalCoords(Vector(0.0, 12.0, 12.0)).toTuple(), 2
|
||||
)
|
||||
|
||||
p = Plane.XZ(origin=(2, 0, 2))
|
||||
r = p.toWorldCoords((1.0, 1.0)).toTuple()
|
||||
self.assertTupleAlmostEquals((3.0, 0.0, 3.0), r, 2)
|
||||
self.assertTupleAlmostEquals((10.0, 10.0), p.toLocalCoords(
|
||||
Vector(12.0, 0.0, 12.0)).toTuple(), 2)
|
||||
self.assertTupleAlmostEquals(
|
||||
(10.0, 10.0), p.toLocalCoords(Vector(12.0, 0.0, 12.0)).toTuple(), 2
|
||||
)
|
||||
|
||||
def testXYPlaneBasics(self):
|
||||
p = Plane.named('XY')
|
||||
p = Plane.named("XY")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
|
||||
def testYZPlaneBasics(self):
|
||||
p = Plane.named('YZ')
|
||||
p = Plane.named("YZ")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
|
||||
def testZXPlaneBasics(self):
|
||||
p = Plane.named('ZX')
|
||||
p = Plane.named("ZX")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
|
||||
def testXZPlaneBasics(self):
|
||||
p = Plane.named('XZ')
|
||||
p = Plane.named("XZ")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), yInvAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
|
||||
def testYXPlaneBasics(self):
|
||||
p = Plane.named('YX')
|
||||
p = Plane.named("YX")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), zInvAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
||||
|
||||
def testZYPlaneBasics(self):
|
||||
p = Plane.named('ZY')
|
||||
p = Plane.named("ZY")
|
||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
Reference in New Issue
Block a user