From 73db6ca713548d466a6191fb0c502448778d7a51 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk Date: Thu, 21 Feb 2019 20:30:07 +0100 Subject: [PATCH 1/5] Support for text rendering in the impl layer --- cadquery/occ_impl/shapes.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 6780ff30d..fa9219fb3 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -1,4 +1,4 @@ -from cadquery import Vector, BoundBox +from cadquery import Vector, BoundBox, Plane import OCC.Core.TopAbs as ta # Tolopolgy type enum import OCC.Core.GeomAbs as ga # Geometry type enum @@ -106,6 +106,11 @@ from OCC.BRepCheck import BRepCheck_Analyzer +from OCC.Core.Addons import (text_to_brep, + Font_FA_Regular, + Font_FA_Italic, + Font_FA_Bold) + from math import pi, sqrt TOLERANCE = 1e-6 @@ -1451,6 +1456,24 @@ def makeCompound(cls, listOfShapes): comp_builder.Add(comp, s.wrapped) return cls(comp) + + @classmethod + def makeText(cls, text, size, height, font="Arial", kind='regular', + position=Plane.XY()): + """ + Create a 3D text + """ + + font_kind = {'regular' : Font_FA_Regular, + 'bold' : Font_FA_Bold, + 'italic' : Font_FA_Italic}[kind] + + text_flat = Shape(text_to_brep(text, font, font_kind, size, False)) + vecNormal = text_flat.Faces()[0].normalAt()*height + + text_3d = BRepPrimAPI_MakePrism(text_flat.wrapped, vecNormal.wrapped) + + return cls(text_3d.Shape()).transformShape(position.fG) # TODO this is likely not needed if sing PythonOCC.Core.correclty but we will see From a26b4017d51d22848a65fa0edd07a7ea12aac08f Mon Sep 17 00:00:00 2001 From: adam-urbanczyk Date: Thu, 21 Feb 2019 21:05:47 +0100 Subject: [PATCH 2/5] Text support at Workplane level --- cadquery/cq.py | 53 +++++++++++++++++++++++++++++++++++++ cadquery/occ_impl/shapes.py | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index fc613df4f..211dd48ce 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -2268,6 +2268,20 @@ def _combineWithBase(self, obj): r = baseSolid.fuse(obj) return self.newObject([r]) + + def _cutFromBase(self, obj): + """ + Cuts the provided object from the base solid, if one can be found. + :param obj: + :return: a new object that represents the result of combining the base object with obj, + or obj if one could not be found + """ + baseSolid = self.findSolid(searchParents=True) + r = obj + if baseSolid is not None: + r = baseSolid.cut(obj) + + return self.newObject([r]) def combine(self, clean=True): """ @@ -2770,6 +2784,45 @@ def clean(self): raise AttributeError( "%s object doesn't support `clean()` method!" % obj.ShapeType()) return self.newObject(cleanObjects) + + def text(self, txt, fontsize, distance, cut=True, combine=False, clean=True, + font="Arial", kind='regular'): + """ + Create a 3D text + + :param str txt: text to be rendered + :param distance: the distance to extrude, normal to the workplane plane + :type distance: float, negative means opposite the normal direction + :param float fontsize: size of the font + :param boolean cut: True to cut the resulting solid from the parent solids if found. + :param boolean combine: True to combine the resulting solid with parent solids if found. + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape + :param str font: fontname (default: Arial) + :param str kind: font type (default: Normal) + :return: a CQ object with the resulting solid selected. + + extrude always *adds* material to a part. + + The returned object is always a CQ object, and depends on wither combine is True, and + whether a context solid is already defined: + + * if combine is False, the new value is pushed onto the stack. + * if combine is true, the value is combined with the context solid if it exists, + and the resulting solid becomes the new context solid. + + """ + r = Compound.makeText(txt,fontsize,distance,font=font,kind=kind, + position=self.plane) + + if cut: + newS = self._cutFromBase(r) + elif combine: + newS = self._combineWithBase(r) + else: + newS = self.newObject([r]) + if clean: + newS = newS.clean() + return newS def _repr_html_(self): """ diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index fa9219fb3..ef11ede3e 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -1473,7 +1473,7 @@ def makeText(cls, text, size, height, font="Arial", kind='regular', text_3d = BRepPrimAPI_MakePrism(text_flat.wrapped, vecNormal.wrapped) - return cls(text_3d.Shape()).transformShape(position.fG) + return cls(text_3d.Shape()).transformShape(position.rG) # TODO this is likely not needed if sing PythonOCC.Core.correclty but we will see From e474c0ef6f1e4c417d34506ca0ea3f1a8c1c6c37 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk Date: Thu, 21 Feb 2019 21:17:54 +0100 Subject: [PATCH 3/5] Added tests of the text feature --- tests/TestCadQuery.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 0f03aae05..b4588d81f 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1800,3 +1800,17 @@ def testClose(self): # The obj1 shape shall have the same volume as the obj2 shape. self.assertAlmostEqual(obj1.val().Volume(), obj2.val().Volume()) + + def testText(self): + + box = Workplane("XY" ).box(4, 4, 0.5) + + obj1 = box.faces('>Z').workplane().text('CQ 2.0',0.5,-.05,cut=True) + + #combined object should have smaller volume + self.assertGreater(box.val().Volume(),obj1.val().Volume()) + + obj2 = box.faces('>Z').workplane().text('CQ 2.0',0.5,.05,cut=False,combine=True) + #combined object should have bigger volume + self.assertLess(box.val().Volume(),obj2.val().Volume()) + From 4a9bd81b89566b911e5d51b5d0e32b606cdb2e26 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk Date: Fri, 22 Feb 2019 06:54:03 +0100 Subject: [PATCH 4/5] Added number of top faces check in the text test case --- tests/TestCadQuery.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index b4588d81f..379703f19 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1810,7 +1810,11 @@ def testText(self): #combined object should have smaller volume self.assertGreater(box.val().Volume(),obj1.val().Volume()) - obj2 = box.faces('>Z').workplane().text('CQ 2.0',0.5,.05,cut=False,combine=True) + obj2 = box.faces('>Z').workplane()\ + .text('CQ 2.0',0.5,.05,cut=False,combine=True) + #combined object should have bigger volume self.assertLess(box.val().Volume(),obj2.val().Volume()) - + + #verify that the number of top faces is correct (NB: this is font specific) + self.assertEqual(len(obj2.faces('>Z').vals()),5) \ No newline at end of file From 26a74c69bb645340efed555337a65a82cb76e666 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk Date: Fri, 22 Feb 2019 07:01:43 +0100 Subject: [PATCH 5/5] Test standalone text generation. --- tests/TestCadQuery.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 379703f19..a03dbb335 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1817,4 +1817,10 @@ def testText(self): self.assertLess(box.val().Volume(),obj2.val().Volume()) #verify that the number of top faces is correct (NB: this is font specific) - self.assertEqual(len(obj2.faces('>Z').vals()),5) \ No newline at end of file + self.assertEqual(len(obj2.faces('>Z').vals()),5) + + obj3 = box.faces('>Z').workplane()\ + .text('CQ 2.0',0.5,.05,cut=False,combine=False) + + #verify that the number of solids is correct + self.assertEqual(len(obj3.solids().vals()),5) \ No newline at end of file