add cad class
This commit is contained in:
287
src/fvr/cad.py
Normal file
287
src/fvr/cad.py
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
import ezdxf
|
||||||
|
import ezdxf.addons.drawing
|
||||||
|
import ezdxf.addons.drawing.svg
|
||||||
|
import ezdxf.addons.drawing.properties
|
||||||
|
|
||||||
|
# dxfattribs={'color': 1}
|
||||||
|
# color
|
||||||
|
# 0: white
|
||||||
|
# 1: red
|
||||||
|
# 2: yellow
|
||||||
|
# 3: green
|
||||||
|
# 4: cyan
|
||||||
|
# 5: blue
|
||||||
|
# 6: magenta
|
||||||
|
# 7: white (as 0 - on dark background, black on light background)
|
||||||
|
# 8: gray
|
||||||
|
# 9: light-gray
|
||||||
|
#
|
||||||
|
# 10 - 19 shades of red (10 red same as 1)
|
||||||
|
# ...
|
||||||
|
# 240 - 249 shades of red (slightly blue shift)
|
||||||
|
# 250 - 255 shades of gray
|
||||||
|
# see https://ezdxf.mozman.at/docs/concepts/aci.html#aci
|
||||||
|
|
||||||
|
class doc:
|
||||||
|
"""
|
||||||
|
contour scale and dimension scale
|
||||||
|
|
||||||
|
to create a line in dimension scale = (x, y)/sc*sd
|
||||||
|
"""
|
||||||
|
|
||||||
|
# scale
|
||||||
|
_sd = 1/1 # scale dimension
|
||||||
|
_sc = 1/1 # scale countour
|
||||||
|
|
||||||
|
def __init__(self, setup=True, scale=1, sc=1, sd=1):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
scale: scale line and font size of contour and dimensions
|
||||||
|
sc: scale contour
|
||||||
|
sd: scale dimension position
|
||||||
|
"""
|
||||||
|
self.doc = ezdxf.new(dxfversion='AC1032', setup=setup)
|
||||||
|
self.msp = self.doc.modelspace()
|
||||||
|
layercon = self.doc.layers.add("con")
|
||||||
|
layercon.color = 0
|
||||||
|
layercon.dxf.lineweight = 75
|
||||||
|
layerdim = self.doc.layers.add("dim")
|
||||||
|
layerdim.color = 6
|
||||||
|
layerdim.dxf.lineweight = 35
|
||||||
|
# dimstyle = self.doc.dimstyles.add("dim")
|
||||||
|
# dimstyle.set_text_align(valign='center', vshift=1)
|
||||||
|
# dimstyle.set_dimline_format(color=6)
|
||||||
|
# dimstyle.set_extline_format(color=6)
|
||||||
|
self.sc = self._sc*sc*scale
|
||||||
|
self.sd = self._sd*sd*scale
|
||||||
|
#self.sd = self._sd*sd*scale/sc
|
||||||
|
#self.sd = sc/(self._sd*sd)*scale
|
||||||
|
self.dims = []
|
||||||
|
self._dimstyle()
|
||||||
|
|
||||||
|
def _dimstyle(self):
|
||||||
|
self.dimstyle = {
|
||||||
|
'dimtxsty': 'Standard',
|
||||||
|
# text
|
||||||
|
'dimtxt': 0.35, # 0.35mm line weight
|
||||||
|
'dimclrt': 6, # text color ## set via layer
|
||||||
|
# arrow
|
||||||
|
'dimtsz': 0, # set tick size to 0 to enable arrow usage
|
||||||
|
'dimasz': 0.35, # arrow size in drawing units
|
||||||
|
'dimblk': ezdxf.ARROWS.closed_filled, # arrows
|
||||||
|
# dimension line
|
||||||
|
'dimclrd': 6, # color ## set via layer
|
||||||
|
'dimlwd': 35, # 0.35mm line weight
|
||||||
|
# extension line
|
||||||
|
'dimclre': 6, # color ## set via layer
|
||||||
|
'dimlwe': 35, # 0.35mm line weight
|
||||||
|
'dimexe': 0.15, # length above dimension line
|
||||||
|
'dimex0': 0.10, # offset from measurement point
|
||||||
|
'dimlfac': 1/self.sc, # scale factor for dimension measurements.
|
||||||
|
#'dimlfac': 100*self._sdc*self.sd,
|
||||||
|
}
|
||||||
|
|
||||||
|
def line(
|
||||||
|
self, p1, p2, color=None, lineweight=None, linetype='CONTINUOUS',
|
||||||
|
layer=None, dxfattribs=None, shift=(0, 0)):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
p1: (x, y)
|
||||||
|
p2: (x, y)
|
||||||
|
color: defaults to 0
|
||||||
|
lineweight: defaults to 75, corresponds to a 0.75 mm linewidth (conversion 1/100).
|
||||||
|
linetype: 'DASHED2'
|
||||||
|
leyer: e.g. 'con' for contour, 'dim' dimension
|
||||||
|
"""
|
||||||
|
_dxfattribs={'layer': 'con', 'linetype': linetype}
|
||||||
|
if layer is not None: _dxfattribs.update({'layer': layer})
|
||||||
|
if color is not None: _dxfattribs.update({'color': color})
|
||||||
|
if lineweight is not None: _dxfattribs.update({'lineweight': lineweight})
|
||||||
|
if dxfattribs is not None:
|
||||||
|
_dxfattribs = dxfattribs
|
||||||
|
p1 = ((p1[0]+shift[0])*self.sc, (p1[1]+shift[1])*self.sc)
|
||||||
|
p2 = ((p2[0]+shift[0])*self.sc, (p2[1]+shift[1])*self.sc)
|
||||||
|
self.msp.add_line(p1, p2, dxfattribs=_dxfattribs)
|
||||||
|
|
||||||
|
def polyline(
|
||||||
|
self, pts, close=False, color=None, lineweight=None, linetype='CONTINUOUS',
|
||||||
|
layer=None, dxfattribs=None, shift=(0, 0)):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
pts: [(x1, y1), (x2, y2), ...]
|
||||||
|
"""
|
||||||
|
_dxfattribs={'layer': 'con', 'linetype': linetype}
|
||||||
|
if layer is not None: _dxfattribs.update({'layer': layer})
|
||||||
|
if color is not None: _dxfattribs.update({'color': color})
|
||||||
|
if lineweight is not None: _dxfattribs.update({'lineweight': lineweight})
|
||||||
|
if dxfattribs is not None:
|
||||||
|
_dxfattribs = dxfattribs
|
||||||
|
pts = [((x+shift[0])*self.sc, (y+shift[1])*self.sc) for x, y in pts]
|
||||||
|
self.msp.add_lwpolyline(pts, close=close, dxfattribs=_dxfattribs)
|
||||||
|
|
||||||
|
def circle(
|
||||||
|
self, pt, radius, color=None, lineweight=None, linetype='CONTINUOUS',
|
||||||
|
layer=None, dxfattribs=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
pt: (x, y)
|
||||||
|
radius:
|
||||||
|
"""
|
||||||
|
_dxfattribs={'layer': 'con', 'linetype': linetype}
|
||||||
|
if layer is not None: _dxfattribs.update({'layer': layer})
|
||||||
|
if color is not None: _dxfattribs.update({'color': color})
|
||||||
|
if lineweight is not None: _dxfattribs.update({'lineweight': lineweight})
|
||||||
|
if dxfattribs is not None:
|
||||||
|
_dxfattribs = dxfattribs
|
||||||
|
pt = [i*self.sc for i in pt]
|
||||||
|
radius *= self.sc
|
||||||
|
self.msp.add_circle(pt, radius=radius, dxfattribs=_dxfattribs)
|
||||||
|
|
||||||
|
def linear_dim(self, base, p1, p2, text, dim_shift=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
base: (x, y) location of the dimension line in contour scale.
|
||||||
|
Use dim_shift to be able to scale contour only and not the dimension.
|
||||||
|
p1: (x, y) 1st measurement point
|
||||||
|
p2: (x, y) 2nd measurement point
|
||||||
|
dim_shift: shift (dx, dy), in dimension scale not contour scale
|
||||||
|
"""
|
||||||
|
_dxfattribs={'layer': 'dim'}
|
||||||
|
base = [i*self.sc for i in base]
|
||||||
|
p1, p2 = [i*self.sc for i in p1], [i*self.sc for i in p2]
|
||||||
|
if dim_shift is not None:
|
||||||
|
dim_shift = [i*self.sd for i in dim_shift]
|
||||||
|
base = ((base[0]+dim_shift[0]), (base[1]+dim_shift[1]))
|
||||||
|
dim = self.msp.add_linear_dim(base=base, p1=p1, p2=p2, text=text,
|
||||||
|
# dimstyle="EZDXF", # default dimension style
|
||||||
|
# dimstyle="dim",
|
||||||
|
override=self.dimstyle,
|
||||||
|
dxfattribs=_dxfattribs,
|
||||||
|
)
|
||||||
|
self.dims.append(dim)
|
||||||
|
# dim.render() below when fetching the rendering
|
||||||
|
|
||||||
|
def aligned_dim(
|
||||||
|
self, p1, p2, distance, text, text_rotation=None, text_shifth=0,
|
||||||
|
text_shiftv=0, arrow1=None, arrow2=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
p1: (x, y) 1st measurement point
|
||||||
|
p2: (x, y) 2nd measurement point
|
||||||
|
distance: NOTE! in dimension scale not contour scale
|
||||||
|
text:
|
||||||
|
text_rotation: global rotation in in degree
|
||||||
|
text_shifth: shift parallel to the dimension line
|
||||||
|
text_shiftv: shift perpendicular to the dimension line
|
||||||
|
arrow1: 'DOTSMALL'
|
||||||
|
arrow2: 'DOTSMALL'
|
||||||
|
"""
|
||||||
|
_dxfattribs = {'layer': 'dim'}
|
||||||
|
if text_rotation is not None: _dxfattribs.update({'text_rotation': text_rotation})
|
||||||
|
p1, p2 = [i*self.sc for i in p1], [i*self.sc for i in p2]
|
||||||
|
# distance # not distance, this is in dimension scale
|
||||||
|
distance *= self.sd
|
||||||
|
text_shifth *= self.sd
|
||||||
|
text_shiftv *= self.sd
|
||||||
|
dim = self.msp.add_aligned_dim(
|
||||||
|
p1=p1, p2=p2, distance=distance, text=text, override=self.dimstyle,
|
||||||
|
dxfattribs=_dxfattribs)
|
||||||
|
dim.shift_text(dh=text_shifth, dv=text_shiftv)
|
||||||
|
if arrow1 is not None:
|
||||||
|
dim.set_arrows(blk1=arrow1)
|
||||||
|
if arrow2 is not None:
|
||||||
|
dim.set_arrows(blk2=arrow2)
|
||||||
|
self.dims.append(dim)
|
||||||
|
# dim.render() below when fetching the rendering
|
||||||
|
|
||||||
|
def leader(
|
||||||
|
self, pts=(0, 0), layer=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
pts: [(x1, y1), (x2, y2), ...]
|
||||||
|
distance: NOTE! in dimension scale not contour scale
|
||||||
|
text:
|
||||||
|
text_rotation: global rotation in in degree
|
||||||
|
text_shifth: shift parallel to the dimension line
|
||||||
|
text_shiftv: shift perpendicular to the dimension line
|
||||||
|
arrow1: 'DOTSMALL'
|
||||||
|
arrow2: 'DOTSMALL'
|
||||||
|
"""
|
||||||
|
_dxfattribs = {'layer': 'dim'}
|
||||||
|
pts = [((x)*self.sc, (y)*self.sc) for x, y in pts]
|
||||||
|
dim = self.msp.add_leader(
|
||||||
|
vertices=pts, override=self.dimstyle,
|
||||||
|
dxfattribs=_dxfattribs)
|
||||||
|
|
||||||
|
def text(
|
||||||
|
self, text, pt=(0, 0), height=0.35, rotation=0, layer=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
p1: (x, y) 1st measurement point
|
||||||
|
p2: (x, y) 2nd measurement point
|
||||||
|
distance: NOTE! in dimension scale not contour scale
|
||||||
|
text:
|
||||||
|
text_rotation: global rotation in in degree
|
||||||
|
text_shifth: shift parallel to the dimension line
|
||||||
|
text_shiftv: shift perpendicular to the dimension line
|
||||||
|
arrow1: 'DOTSMALL'
|
||||||
|
arrow2: 'DOTSMALL'
|
||||||
|
"""
|
||||||
|
_dxfattribs = {'layer': ''}
|
||||||
|
if layer is not None: _dxfattribs.update({'layer': layer})
|
||||||
|
pt = [i*self.sc for i in pt]
|
||||||
|
dim = self.msp.add_text(
|
||||||
|
text=text, height=height, rotation=rotation, dxfattribs=_dxfattribs)
|
||||||
|
# set position and alignment
|
||||||
|
dim.set_placement(
|
||||||
|
pt,
|
||||||
|
# align=ezdxf.enums.TextEntityAlignment.MIDDLE_CENTER,
|
||||||
|
# align=ezdxf.enums.TextEntityAlignment.MIDDLE_LEFT,
|
||||||
|
align=ezdxf.enums.TextEntityAlignment.LEFT,
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_dims(self):
|
||||||
|
# Necessary second step to create the BLOCK entity with the dimension geometry.
|
||||||
|
# Additional processing of the DIMENSION entity could happen between adding
|
||||||
|
# the entity and the rendering call.
|
||||||
|
[dim.render() for dim in self.dims]
|
||||||
|
|
||||||
|
def save_dxf(self, filename="output.dxf"):
|
||||||
|
self.render_dims()
|
||||||
|
self.doc.saveas(filename)
|
||||||
|
|
||||||
|
def export_svg_string(self, background="#21283000"):
|
||||||
|
self.render_dims()
|
||||||
|
doc = self.doc
|
||||||
|
msp = self.msp
|
||||||
|
context = ezdxf.addons.drawing.RenderContext(doc)
|
||||||
|
backend = ezdxf.addons.drawing.svg.SVGBackend()
|
||||||
|
layoutprops = ezdxf.addons.drawing.properties.LayoutProperties.from_layout(msp)
|
||||||
|
layoutprops.set_colors(background) # set background, transparent "#21283000", default "#212830"
|
||||||
|
frontend = ezdxf.addons.drawing.Frontend(context, backend)
|
||||||
|
frontend.draw_layout(msp, layout_properties=layoutprops)
|
||||||
|
# page size
|
||||||
|
# page = ezdxf.addons.drawing.layout.Page(
|
||||||
|
# 210, 297, units=ezdxf.addons.drawing.layout.Units.mm,
|
||||||
|
# margins=ezdxf.addons.drawing.layout.Margins.all(20))
|
||||||
|
# svg_string = backend.get_string(page)
|
||||||
|
# autodetect page size
|
||||||
|
page = ezdxf.addons.drawing.layout.Page(
|
||||||
|
0, 0, units=ezdxf.addons.drawing.layout.Units.mm,
|
||||||
|
margins=ezdxf.addons.drawing.layout.Margins.all(2))
|
||||||
|
svg_string = backend.get_string(
|
||||||
|
page, settings=ezdxf.addons.drawing.layout.Settings(scale=20/1, fit_page=False))
|
||||||
|
return svg_string
|
||||||
|
|
||||||
|
def export_svg(self, filename="output.svg"):
|
||||||
|
doc = self.doc
|
||||||
|
svg_string = export_svg_string(doc)
|
||||||
|
with open(filename, "wt", encoding="utf8") as fh:
|
||||||
|
fh.write(svg_string)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
dd = doc()
|
||||||
|
# detects the bounding box for any Iterable[DXFEntity]
|
||||||
|
bounding_box = ezdxf.bbox.extents(dd.modelspace())
|
||||||
|
print(bounding_box)
|
||||||
|
print('\n'.join([f"{i}" for i in dir(dd.doc)]))
|
||||||
Reference in New Issue
Block a user